Reader small image

You're reading from  Java Coding Problems - Second Edition

Product typeBook
Published inMar 2024
PublisherPackt
ISBN-139781837633944
Edition2nd Edition
Right arrow
Author (1)
Anghel Leonard
Anghel Leonard
author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard

Right arrow

43. Invoking default methods from Proxy instances

Starting with JDK 8, we can define default methods in interfaces. For instance, let’s consider the following interfaces (for brevity, all methods from these interfaces are declared as default):

Figure 2.26.png

Figure 2.26: Interfaces: Printable, Writable, Draft, and Book

Next, let’s assume that we want to use the Java Reflection API to invoke these default methods. As a quick reminder, the Proxy class goal is used to provide support for creating dynamic implementations of interfaces at runtime.

That being said, let’s see how we can use the Proxy API for calling our default methods.

JDK 8

Calling a default method of an interface in JDK 8 relies on a little trick. Basically, we create from scratch a package-private constructor from the Lookup API. Next, we make this constructor accessible – this means that Java will not check the access modifiers to this constructor and, therefore, will not throw an IllegalAccessException when we try to use it. Finally, we use this constructor to wrap an instance of an interface (for instance, Printable) and use reflective access to the default methods declared in this interface.

So, in code lines, we can invoke the default method Printable.print() as follows:

// invoke Printable.print(String)
Printable pproxy = (Printable) Proxy.newProxyInstance(
  Printable.class.getClassLoader(),
  new Class<?>[]{Printable.class}, (o, m, p) -> {
    if (m.isDefault()) {
      Constructor<Lookup> cntr = Lookup.class
        .getDeclaredConstructor(Class.class);
      cntr.setAccessible(true);
      return cntr.newInstance(Printable.class)
                 .in(Printable.class)
                 .unreflectSpecial(m, Printable.class)
                 .bindTo(o)
                 .invokeWithArguments(p);
      }
      return null;
  });
// invoke Printable.print()
pproxy.print("Chapter 2");

Next, let’s focus on the Writable and Draft interfaces. Draft extends Writable and overrides the default write()method. Now, every time we explicitly invoke the Writable.write() method, we expect that the Draft.write() method is invoked automatically behind the scenes. A possible implementation looks as follows:

// 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")) {
    Constructor<Lookup> cntr = Lookup.class
     .getDeclaredConstructor(Class.class);
    cntr.setAccessible(true); 
    cntr.newInstance(Draft.class)
        .in(Draft.class)
        .findSpecial(Draft.class, "write",
           MethodType.methodType(void.class, String.class), 
           Draft.class)
        .bindTo(o)
        .invokeWithArguments(p);
    return cntr.newInstance(Writable.class)
        .in(Writable.class)
        .findSpecial(Writable.class, "write",
           MethodType.methodType(void.class, String.class), 
           Writable.class)
        .bindTo(o)
        .invokeWithArguments(p);
    }
    return null;
  });
// invoke Writable.write(String)
dpproxy.write("Chapter 1");

Finally, let’s focus on the Printable and Book interfaces. Book extends Printable and doesn’t define any methods. So, when we call the inherited print() method, we expect that the Printable.print() method is invoked. While you can check this solution in the bundled code, let’s focus on the same tasks using JDK 9+.

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 the default in JDK 9) and deny, we have two more values: warn and debug. However, --illegal-access=permit; support was removed in JDK 17.

In this context, the previous code may not work in JDK 9+, or it might still work but you’ll see a warning such 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() method 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 scenes:

// 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.

Previous PageNext Page
You have been reading a chapter from
Java Coding Problems - Second Edition
Published in: Mar 2024Publisher: PacktISBN-13: 9781837633944
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard