Invoking methods

In this example, we used only one single reflection call to get the annotations attached to a class. Reflection can do many more things. Handling annotations is the most important use for these calls since annotations do not have their own functionality and cannot be handled in any other way during runtime. Reflection, however, does not stop telling us what annotations a class or any other annotable element has. Reflection can be used to get a list of the methods of a class, the name of the methods as strings, the implemented interfaces of a class, the parent class it extends, the fields, the types of fields, and so on. Reflection generally provides methods and classes to walk through the actual code structure down to the method level, programmatically.

This walkthrough does not only allow reading types and code structure but also makes it possible to set field values and call methods without knowing the methods' name at compile time. We can even set fields that are private and are not generally accessible by the outside world. It is also to note that accessing the methods and fields through reflection is usually slower than through compiled code because it always involves lookup by the name of the element in the code.

The rule of thumb is that if you see that you have to create code using reflection, then realize that you are probably creating a framework (or writing a book about Java that details reflection). Does it sound familiar?

Spring also uses reflection to discover the classes, methods, and fields, and also to inject an object. It uses the URL class loader to list all the JAR files and directories that are on the class path, loads them, and examines the classes.

For a contrived example, for the sake of demonstration, let's assume that the ConsistencyChecker implementations were written by many external software vendors, and the architect who originally designed the program structure just forgot to include the isConsistent method in the interface. (At the same time, to save our mental health, we can also imagine that this person is not working anymore in the company for doing so.) As a consequence, the different vendors delivered Java classes that "implement" this interface but we cannot invoke the method, not only because we do not have a common parent interface that has this method but also because the vendors just happened to use different names for their methods.

What can we do in this situation? Business-wise, asking all the vendors to rewrite their checkers is ruled out because them knowing we are in trouble attaches a hefty price tag to the task. Our managers want to avoid that cost and we developers also want to show that we can mend the situation and do miracles. (Later, I will have a comment on that.)

We could just have a class that knows every checker and how to invoke each of them in many different ways. This would require us to maintain the said class whenever a new checker is introduced to the system, and we want to avoid that. The whole plugin architecture we are using was invented for this very purpose in the first place.

How can we invoke a method on an object that we know has only one declared method, which accepts an order as a parameter? That is where reflection comes into the picture. Instead of calling checker.isInconsistent(order), we implement a small private method, isInconsistent, which calls the method, whatever its name is, via reflection:

private boolean isInconsistent(ConsistencyChecker checker, Order order) { 
Method[] methods = checker.getClass().getDeclaredMethods();
if (methods.length != 1) {
log.error(
"The checker {} has zero or more than one methods",
checker.getClass());
return false;
}
final Method method = methods[0];
final boolean inconsistent;
try {
inconsistent = (boolean) method.invoke(checker, order);
} catch (InvocationTargetException |
IllegalAccessException |
ClassCastException e) {
log.error("Calling the method {} on class {} threw exception",
method, checker.getClass());
log.error("The exception is ", e);
return false;
}
return inconsistent;
}

We can get the class of the object by calling the getClass method, and on the object that represents the class itself, we can call getDeclaredMethods. Fortunately, the checker classes are not littered by many methods, so we check that there is only one method declared in the checker class. Note that there is also a getMethods method in the reflection library but it always will return more than one method. It returns the declared and the inherited methods. Because each and every class inherits from java.lang.Object, at least the methods of the Object class will be there.

After this, we try to invoke the class using the Method object that represents the method in the reflection class. Note that this Method object is not directly attached to an instance. We retrieved the method from the class, and thus, when we invoke it, we should pass the object it should work on as a first parameter. This way, x.y(z), becomes method.invoke(x,z). The last parameter of invoke is a variable number of arguments that are passed as an Object array. In most cases, when we invoke a method, we know the arguments in our code even if we do not know the name of the method and have to use reflection. When even the arguments are not known but are available as a matter of calculation, then we have to pass them as an Object array.

Invoking a method via reflection is a risky call. If we try to call a method the normal way, which is private, then the compiler will signal an error. If the number of arguments or types are not appropriate, the compiler will again will give us an error. If the returned value is not boolean, or there is no return value at all, then we again get a compiler error. In the case of reflection, the compiler is clueless. It does not know what method we will invoke when the code is executing. The invoke method, on the other hand, can and will notice all these failures when it is invoked. If any of the aforementioned problems occur, then we will get exceptions. If the invoke method itself sees that it cannot perform what we ask of it, then it will throw InvocationTargetException or IllegalAccessException. If the conversion from the actual return value to boolean is not possible, then we will get ClassCastException.

About doing magic, it is a natural urge that we feel like making something extraordinary, something outstanding. This is okay when we are experimenting with something, doing a hobby job. On the other hand, this is strongly not okay when we are working on a professional job. Average programmers, who do not understand your brilliant solution, will maintain the code in an enterprise environment. They will turn your nicely combed code into haystack while fixing some bugs or implementing some minor new features. Even if you are the Mozart of programming, they will be, at best, no-name singers. A brilliant code in an enterprise environment can be a requiem, with all the implications of that metaphor.

Last but not least, the sad reality is that we are usually not the Mozarts of programming.

Note that in case the return value of the original value is primitive, then it will be converted to an object by reflection, and then we will convert it back to the primitive value. If the method does not have a return value, in other words, if it is void, then the reflection will return a java.lang.Void object. The Void object is only a placeholder. We cannot convert it to any primitive value or any other type of objects. It is needed because Java is strict and invoke has to return an Object, so the runtime needs something that it can return. All we can do is check that the returned value class is really Void.

Let's go on with the storyline and our solution. We submitted the code and it works in production for a while till a new update from a software vendor breaks it. We debug the code in the test environment and see that the class now contains more than one method. Our documentation clearly states that they should only have one public method, and they provided a code that has...hmm...we realize that the other methods are private. They are right; they can have private methods according to the contract, so we have to amend the code. We replace the lines that look up the one and only method:

Method[] methods = checker.getClass().getDeclaredMethods(); 
if (methods.length != 1) {
...
}
final Method method = methods[0];

The new code will be as follows:

final Method method = getSingleDeclaredPublicMethod(checker); 
if (method == null) {
log.error(
"The checker {} has zero or more than one methods",
checker.getClass());
return false;

}

The new method we write to look up the one and only public method is as follows:

private Method getSingleDeclaredPublicMethod( 
ConsistencyChecker checker) {
final Method[] methods =
checker.getClass().getDeclaredMethods();
Method singleMethod = null;
for (Method method : methods) {
if (Modifier.isPublic(method.getModifiers())) {
if (singleMethod != null) {
return null;
}
singleMethod = method;
}
}
return singleMethod;
}

To check whether the method is public or not, we use a static method from the Modifier class. There are methods to check all possible modifiers. The value that the getModifiers method returns is an int bit field. Different bits have different modifiers and there are constants that define these. This simplification leads to inconsistency, which you can check if a method is an interface or volatile, that is, actually nonsense. The fact is that bits that can only be used for other types of reflection objects will never be set.

There is one exception, which is volatile. This bit is reused to signal bridge methods. Bridge methods are created by the compiler automatically and can have deep and complex issues that we do not discuss in this book. The reuse of the same bit does not cause confusion because a field can be volatile, but as a field, it cannot be a bridge method. Obviously, a field is a field and not a method. In the same way, a method cannot be a volatile field. The general rule is: do not use methods on reflection objects where they do not have a meaning; or else, know what you do.

Making the storyline even more intricate, a new version of a checker accidentally implements the checking method as a package private. The programmer simply forgot to use the public keyword. For the sake of simplicity, let's assume that the classes declare only one method again, but it is not public. How do we solve this problem using reflection?

Obviously, the simplest solution is to ask the vendors to fix the problem: it is their fault. In some cases, however, we must create a workaround over some problems. There is another solution: creating a class with a public method in the same package, invoking the package private methods from the other class, thus relaying the other class. As a matter of fact, this solution, as a workaround for such a bug, seems to be more logical and cleaner, but this time, we want to use reflection.

To avoid java.lang.IllegalAccessException, we have to set the method object as accessible. To do so, we have to insert the following line in front of the invocation:

method.setAccessible(true);

Note that this will not change the method to public. It will only make the method accessible for invocation through the very instance of the method object that we set as accessible.

I have seen code that checks whether a method is accessible or not by calling the isAccessible method and saves this information; it sets the method as accessible if it was not accessible and restores the original accessibility after the invocation. This is totally useless. As soon as the method variable goes out of scope, and there is no reference to the object we set the accessibility flag to, the effect of the setting wears off. Also, there is no penalty for setting the accessibility of a public or an otherwise callable method.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.103.96