Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to explicitly invoke default method from a dynamic Proxy?

Since Java 8 interfaces could have default methods. I know how to invoke the method explicitly from the implementing method, i.e. (see Explicitly calling a default method in Java)

But how do I explicitly invoke the default method using reflection for example on a proxy?

Example:

interface ExampleMixin {    String getText();    default void printInfo(){     System.out.println(getText());   } }  class Example {    public static void main(String... args) throws Exception {      Object target = new Object();      Map<String, BiFunction<Object, Object[], Object>> behavior = new HashMap<>();      ExampleMixin dynamic =             (ExampleMixin) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{ExampleMixin.class}, (Object proxy, Method method, Object[] arguments) -> {                  //custom mixin behavior                 if(behavior.containsKey(method.getName())) {                     return behavior.get(method.getName()).apply(target, arguments);                 //default mixin behavior                 } else if (method.isDefault()) {                     //this block throws java.lang.IllegalAccessException: no private access for invokespecial                     return MethodHandles.lookup()                                         .in(method.getDeclaringClass())                                         .unreflectSpecial(method, method.getDeclaringClass())                                         .bindTo(target)                                         .invokeWithArguments();                 //no mixin behavior                 } else if (ExampleMixin.class == method.getDeclaringClass()) {                     throw new UnsupportedOperationException(method.getName() + " is not supported");                 //base class behavior                 } else{                     return method.invoke(target, arguments);                 }             });      //define behavior for abstract method getText()     behavior.put("getText", (o, a) -> o.toString() + " myText");      System.out.println(dynamic.getClass());     System.out.println(dynamic.toString());     System.out.println(dynamic.getText());      //print info should by default implementation     dynamic.printInfo();   } } 

Edit: I know a similar question has been asked in How do I invoke Java 8 default methods refletively, but this has not solved my problem for two reasons:

  • the problem described in that question aimed on how to invoked it via reflection in general - so no distinction between default and overriden method was made - and this is simple, you only need an instance.
  • one of the answers - using method handles - does only work with nasty hack (imho) like changing access modifiers to fields of the lookup class, which is the same category of "solutions" like this: Change private static final field using Java reflection: it's good to know it's possible, but I wouldn't use it in production - I'm looking for an "official" way to do it.

The IllegalAccessException is thrown in unreflectSpecial

Caused by: java.lang.IllegalAccessException: no private access for invokespecial: interface example.ExampleMixin, from example.ExampleMixin/package at java.lang.invoke.MemberName.makeAccessException(MemberName.java:852) at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1568) at java.lang.invoke.MethodHandles$Lookup.unreflectSpecial(MethodHandles.java:1227) at example.Example.lambda$main$0(Example.java:30) at example.Example$$Lambda$1/1342443276.invoke(Unknown Source) 
like image 800
Gerald Mücke Avatar asked Jun 14 '16 12:06

Gerald Mücke


People also ask

How will you call the default method of interface without implementation?

A class can override a default interface method and call the original method by using super , keeping it nicely in line with calling a super method from an extended class. But there is one catch, you need to put the name of the interface before calling super this is necessary even if only one interface is added.

What is a dynamic proxy invocation handler?

A dynamic proxy can be thought of as a kind of Facade, but one that can pretend to be an implementation of any interface. Under the cover, it routes all method invocations to a single handler – the invoke() method.

Can you overload a default method?

you can override a default method of an interface from the implementing class.

How does JDK dynamic proxy work?

JDK proxy (dynamic proxy): The JDK proxy creates a new proxy object by implementing interfaces of the target object and delegating method calls. CGLIB proxy: The CGLIB proxy creates a new proxy object by extending the target object and delegating method calls.


2 Answers

I've been troubled by similar issues as well when using MethodHandle.Lookup in JDK 8 - 10, which behave differently. I've blogged about the correct solution here in detail.

This approach works in Java 8

In Java 8, the ideal approach uses a hack that accesses a package-private constructor from Lookup:

import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Constructor; import java.lang.reflect.Proxy;  interface Duck {     default void quack() {         System.out.println("Quack");     } }  public class ProxyDemo {     public static void main(String[] a) {         Duck duck = (Duck) Proxy.newProxyInstance(             Thread.currentThread().getContextClassLoader(),             new Class[] { Duck.class },             (proxy, method, args) -> {                 Constructor<Lookup> constructor = Lookup.class                     .getDeclaredConstructor(Class.class);                 constructor.setAccessible(true);                 constructor.newInstance(Duck.class)                     .in(Duck.class)                     .unreflectSpecial(method, Duck.class)                     .bindTo(proxy)                     .invokeWithArguments(args);                 return null;             }         );          duck.quack();     } } 

This is the only approach that works with both private-accessible and private-inaccessible interfaces. However, the above approach does illegal reflective access to JDK internals, which will no longer work in a future JDK version, or if --illegal-access=deny is specified on the JVM.

This approach works on Java 9 and 10, but not 8

import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Proxy;  interface Duck {     default void quack() {         System.out.println("Quack");     } }  public class ProxyDemo {     public static void main(String[] a) {         Duck duck = (Duck) Proxy.newProxyInstance(             Thread.currentThread().getContextClassLoader(),             new Class[] { Duck.class },             (proxy, method, args) -> {                 MethodHandles.lookup()                     .findSpecial(                           Duck.class,                           "quack",                            MethodType.methodType(void.class, new Class[0]),                            Duck.class)                     .bindTo(proxy)                     .invokeWithArguments(args);                 return null;             }         );          duck.quack();     } } 

Solution

Simply implement both of the above solutions and check if your code is running on JDK 8 or on a later JDK and you'll be fine. Until you're not :)

like image 158
Lukas Eder Avatar answered Oct 03 '22 16:10

Lukas Eder


If you use a concrete impl class as lookupClass and caller for the invokeSpecial it should correctly invoke the default implementation of the interface (no hack for private access needed):

Example target = new Example(); ...  Class targetClass = target.getClass(); return MethodHandles.lookup()                     .in(targetClass)                     .unreflectSpecial(method, targetClass)                     .bindTo(target)                     .invokeWithArguments(); 

This of course only works if you have a reference to a concrete object implementing the interface.

Edit: this solution will only work if the class in question (Example in the code above), is private accessible from the caller code, e.g. an anonymous inner class.

The current implementation of the MethodHandles/Lookup class will not allow to call invokeSpecial on any class that is not private accessible from the current caller class. There are various work-arounds available, but all of them require the use of reflection to make constructors/methods accessible, which will probably fail in case a SecurityManager is installed.

like image 23
T. Neidhart Avatar answered Oct 03 '22 15:10

T. Neidhart