JDK 9+, pre JDK 16

As you just saw, before JDK 9, the Java Reflection API provides access to non-public class members. This means that external reflective code (for instance, third-party libraries) can have deep access to JDK internals. But, starting with JDK 9, this is not possible because the new module system relies on strong encapsulation.For a smooth transition from JDK 8 to JDK 9, we can use the –illegal-access option. The values of this option range from deny (sustains strong encapsulation, so no illegal reflective code is permitted) to permit (the most relaxed level of strong encapsulation allowing access to platform modules only from unnamed modules). Between permit (which is default in JDK 9) and deny we have two more values: warn and debug.In this context, the previous code may not work in JDK 9+, or it still may work but you’ll see a warning as WARNING: An illegal reflective access operation has occurred.But, we can “fix” our code to avoid illegal reflective access via MethodHandles. Among its goodies, this class exposes lookup methods for creating method handles for fields and methods. Once we have a Lookup, we can rely on its findSpecial() to gain access to the default methods of an interface.Based on MethodHandles, we can invoke the default method Printable.print() as follows:

// invoke Printable.print(String doc)
Printable pproxy = (Printable) Proxy.newProxyInstance(
  Printable.class.getClassLoader(),
    new Class<?>[]{Printable.class}, (o, m, p) -> {
      if (m.isDefault()) {
       return MethodHandles.lookup()
         .findSpecial(Printable.class, “print”,
           MethodType.methodType(void.class, String.class),
           Printable.class)
         .bindTo(o)
         .invokeWithArguments(p);
      }
      return null;
  });
// invoke Printable.print()
pproxy.print(“Chapter 2”);

While in the bundled code you can see more examples, let’s tackle the same topic starting with JDK 16.

JDK 16+

Starting with JDK 16, we can simplify the previous code thanks to the new static method, InvocationHandler.invokeDefault(). As its name suggests, this method is useful for invoking default methods. In code lines, our previous examples for calling Printable.print() can be simplified via invokeDefault() as follows:

// invoke Printable.print(String doc)
Printable pproxy = (Printable) Proxy.newProxyInstance(
  Printable.class.getClassLoader(),
    new Class<?>[]{Printable.class}, (o, m, p) -> {
      if (m.isDefault()) {
        return InvocationHandler.invokeDefault(o, m, p);
      }
      return null;
  });
// invoke Printable.print()
pproxy.print(“Chapter 2”);

In the next example, every time we explicitly invoke the Writable.write() method, we expect that the Draft.write() method is invoked automatically behind the scene:

// invoke Draft.write(String) and Writable.write(String)
Writable dpproxy = (Writable) Proxy.newProxyInstance(
 Writable.class.getClassLoader(),
  new Class<?>[]{Writable.class, Draft.class}, (o, m, p) -> {
   if (m.isDefault() && m.getName().equals(“write”)) {
    Method writeInDraft = Draft.class.getMethod(
     m.getName(), m.getParameterTypes());
    InvocationHandler.invokeDefault(o, writeInDraft, p);
    return InvocationHandler.invokeDefault(o, m, p);
   }
   return null;
 });
// invoke Writable.write(String)
dpproxy.write(“Chapter 1”);

In the bundled code, you can practice more examples.

Leave a Reply

Your email address will not be published. Required fields are marked *