appendix B
High-level introduction to the reflection API

Reflection allows code to inspect types, methods, fields, annotations, and so forth at run time and to defer the decision about how to use them from compile time to run time. Toward that end, Java’s reflection API offers types like Class, Field, Constructor, Method, Annotation, and others. With them, it’s possible to interact with types that weren’t known at compile time: for example, to create instances of an unknown class and call methods on them.

Reflection and its use cases can quickly become complex, and I’m not going to explain it in detail. Instead, this appendix is intended to give you a high-level understanding of what reflection is, what it looks like in Java, and what you or your dependencies can use it for.

Afterward, you’ll be ready to get started using it or work through longer tutorials, such as Oracle’s The Reflection API trail at https://docs.oracle.com/javase/tutorial/reflect. More important, though, you’ll be prepared to understand the changes the module system makes with regard to reflection, which section 7.1.4 and particularly chapter 12 explore.

Instead of building from the ground up, let’s start with a simple example. The following snippet creates a URL, converts it to a string, and then prints that. Before resorting to reflection, I used plain Java code:

URL url = new URL("http://codefx.org");
String urlString = url.toExternalForm();
System.out.println(urlString);

I decided at compile time (meaning, when I was writing the code) that I wanted to create a URL object and call a method in it. Even though that’s not the most natural way to do it, you can split the first two lines into five steps:

  1. Reference the URL class.
  2. Locate the constructor taking a single string parameter.
  3. Call it with http://codefx.org.
  4. Locate the method toExternalForm.
  5. Call it on the url instance.

The following listing shows how to implement those five steps with Java’s reflection API.

Listing B.1 Reflectively creating a URL and calling toExternalForm on it

Class<?> urlClass = Class.forName("java.net.URL"); 

Constructor<?> urlConstructor 
   = urlClass.getConstructor(String.class); 
Object url = 
     urlConstructor.newInstance("http://codefx.org"); 

Method toExternalFormMethod = 
     urlClass.getMethod("toExternalForm"); 
Object methodCallResult = 
     toExternalFormMethod.invoke(url); 

Using the reflection API is, of course, more cumbersome than writing the code directly. But this way, details that used to be baked into the code (like using URL, or which method is called) become a string parameter. As a consequence, instead of having to settle on URL and toExternalForm at compile time, you could decide which type and method to pick later, when the program is already running.

Most use cases for this occur in “frameworky” environments. Think about JUnit, for example, which wants to execute all methods that are annotated with @Test. Once it finds them, it uses getMethod and invoke to call them. Spring and other web frameworks act similarly when looking for controllers and request mappings. Extensible applications that want to load user-provided plugins at run time are another use case.

Fundamental types and methods

The gateway into the reflection API is Class::forName. In its simple form, this static method takes a fully qualified class name and returns a Class instance for it. You can use that instance to get fields, methods, constructors, and more.

To get a specific constructor, call the getConstructor method with the types of the constructor arguments, as I did earlier. Similarly, a specific method can be accessed by calling getMethod and passing its name as well as the parameter types.

The call to getMethod("toExternalForm") didn’t specify any types because the method has no arguments. Here’s URL.openConnection(Proxy), which takes a Proxy as a parameter:

Class<?> urlClass = Class.forName("java.net.URL");
Method openConnectionMethod = urlClass
    .getMethod("openConnection", Proxy.class);

The instances returned by calls to getConstructor and getMethod are of type Constructor and Method, respectively. To call the underlying member, they offer methods like Constructor::newInstance and Method::invoke. An interesting detail of the latter is that you need to pass the instance on which the method is to be called as the first argument. The other arguments will be passed on to the called method.

Continuing the openConnection example:

openConnectionMethod.invoke(url, someProxy);

If you want to call a static method, the instance argument is ignored and can be null.

In addition to Class, Constructor, and Method, there is also Field, which allows read and write access to instance fields. Calling get with an instance retrieves the value that field has in that instance—the set method sets the specified value in the specified instance.

The URL class has an instance field protocol of type String; for the URL http://codefx.org, it would contain "http". Because it’s private, code like this won’t compile:

URL url = new URL("http://codefx.org");
// no access to a private field ~> compile error
url.protocol = "https";

Here’s how to do the same thing with reflection:

// `Class<?> urlClass` and `Object url` are like before
Field protocolField = urlClass.getDeclaredField("protocol");
Object oldProtocol = protocolField.get(url);
protocolField.set(url, "https");

Although this compiles, it still leads to an IllegalAccessException on the get call, because the protocol field is private. But that doesn’t have to stop you.

Breaking into APIs with setAccessible

One important use case for reflection has always been to break into APIs by accessing nonpublic types, methods, and fields. This is called deep reflection. Developers use it to access data that an API doesn’t make accessible, to work around bugs in their dependencies by twiddling with the internal state, and to dynamically populate instances with the correct values—Hibernate does this, for example.

For deep reflection, you need to do nothing more that call setAccessible(true) on a Method, Constructor, or Field instance before using it:

// `Class<?> urlClass` and `Object url` are like before
Field protocolField = urlClass.getDeclaredField("protocol");
protocolField.setAccessible(true);
Object oldProtocol = field.get(url);
protocolField.set(instance, "https");

One challenge when migrating to the module system is that it takes away reflection’s superpowers, meaning calls to setAccessible are much more likely to fail. For more on that and how to remedy it, check chapter 12.

Annotations mark code for reflection

Annotations are an important part of reflection. In fact, annotations are geared toward reflection. They’re meant to provide metainformation that can be accessed at run time and is then used to shape the program’s behavior. JUnit’s @Test and Spring’s @Controller and @RequestMapping are prime examples.

All important reflection-related types like Class, Field, Constructor, Method, and Parameter implement the AnnotatedElement interface. Its Javadoc contains a thorough explanation of how annotations can relate to these elements (directly present, indirectly present, or associated), but its simplest form is this: the getAnnotations method returns the annotations present on that element in form of an array of Annotation instances, whose members can then be accessed.

But in the context of the module system, how you or the frameworks you depend on process annotations is less important than the underlying fact that they only work with reflection. That means any class you see that has some annotations on it will at some point be reflected over—and if that class is in a module, that won’t necessarily work out of the box.

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

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