37

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)
Community
  • 1
  • 1
Gerald Mücke
  • 10,724
  • 2
  • 50
  • 67
  • 1
    Isn't it a duplicate of this http://stackoverflow.com/questions/22614746/how-do-i-invoke-java-8-default-methods-refletively – Rudziankoŭ Jun 14 '16 at 13:00
  • 2
    "*I'm looking for an "official" way to do it*" I may be mistaken but I am afraid that officially you are not supposed to be able to invoke method from supertype if your subtype overridden it. Lets say that your supertype have `acceptSquare` method which can accept any Squares, but your subtype is *specializing* in handling only red squares so it overriden it accordingly to add test for color (after that it invokes `super.addSquare`). So allowing someone to invoke from outside implementation from supertype (even via reflection) of such method could be big security hole. – Pshemo Jun 14 '16 at 13:58
  • How about mixins - adding functionality to an existing class using a dynamic proxy? i.e. I have an instance and want to add additional functionality by "adding" interfaces with default methods to the instance at runtime. There must be a way – Gerald Mücke Jun 14 '16 at 14:04
  • I updated the example accordingly, wanted to have it as simple as possible in the first place, but hopefully my intent is getting clearer now – Gerald Mücke Jun 14 '16 at 14:21
  • The invocation target for a `default` method must be an instance of that `interface`. In your example code, it’s an `Object`—how is that supposed to work? – Holger Jun 14 '16 at 14:29
  • It's not even getting that far, the exception is thrown in `unreflectedSpecial`, so currently the `bindTo()` can be `null`, `proxy`, anonymous instance of `ExampleMixin` or anything else – Gerald Mücke Jun 14 '16 at 14:32
  • I see. The problem is that [this example](http://stackoverflow.com/q/26206614/2711488) only works, because the interface is an inner class (which reduces the usefulness of a proxy)… – Holger Jun 14 '16 at 14:45
  • But what’s the point of delivering the “mixins” via `default` methods of an interface, when you end up invoking them all via Reflection? – Holger Jun 14 '16 at 15:20
  • given the above example I can add behavior to anaemic domains models such as generated by JAXB and provide either default behavior (from the interfaces) or dynamic behavior (exchangeable functions, scripts, whatever - not talking about security here :) ) – Gerald Mücke Jun 14 '16 at 15:41

8 Answers8

16

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 :)

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • Unfortunately neither method works for Android since Android blocks reflection access to the Java 8 method. :( – Mygod May 30 '20 at 22:06
  • It seems like for Android 8+, you need to call the constructor with the integer parameter set to (`ALL_MODES`) as the other API is greylisted and unusable if you target API 28+. – Mygod May 30 '20 at 23:06
  • @Mygod: This was mostly a JDK question and answer. Feel free to provide an Android specific answer, I'm sure it will be useful for others... – Lukas Eder Jun 01 '20 at 16:15
  • To be clear, the approach outlined for Java 8 will only fail on Java 9, 10, 11 if the JVM is started with `--illegal-access=deny` (which, by default, it is not). Otherwise, this solution will print a warning message on the console, but it will still work. – raner Jul 31 '21 at 19:53
  • You seemed to miss out that the context of the actual question is the Java Platform Module System (JPMS). In this context none of your suggested solutions work as you always get an IllegalAccessException. However, if you can see the Interface from a module, you should be able to invoke default methods (esp. for dynamic proxies) as otherwise the basic concepts of Java are flawed. Seems that users need to upgrade to Java 16/17 and use InvocationHandler.invokeDefault as suggested by @John – Jörg Jan 04 '22 at 19:37
10

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.

T. Neidhart
  • 6,060
  • 2
  • 15
  • 38
  • 1
    hm, this also works with anonymous classes ... but given, I don't know the interfaces before (i.e. because it's a parameter), could I creates instances of anonymous classes dynamically? (proxy seems not to work, byte code generation is not an option either) – Gerald Mücke Jun 14 '16 at 15:43
  • What is `ex` in this example? It must be an instance of `Example` here, so why not just use the `target` instance? Besides that, it has the same restriction as using `ExampleMixin` directly; it only works, if the specified class has an inner class relationship with the surrounding code (the code which invokes `lookup()`). – Holger Jun 14 '16 at 16:33
  • This was a mistake, ex should indeed be replaced by target. – T. Neidhart Jun 14 '16 at 16:35
  • 1
    @Gerald Mücke: there is no point in trying to generate an implementation, regardless of which method you use. When you invoke `MethodHandles.lookup()`, you get a context allowing private access to yourself. As soon as you invoke `in(someOtherClass)` on it with a class which has no inner class relationship to yourself, which includes all kinds of generated classes, you’ll lose the private access property, which is required for `unreflectSpecial`. – Holger Jun 14 '16 at 16:42
  • @Holger: I guess you are right after checking the Java 8 source code for MethodHandles. So there will be no work-around that does not involve some reflection hacks? – T. Neidhart Jun 14 '16 at 17:15
  • When it comes to arbitrary interfaces, not known at compile-time, there seems to be no solution without hacking. – Holger Jun 14 '16 at 17:18
  • Hmm, this works only if the interface is well known and can be implemented in source code. If the interface is just a `Class`, it won't work – Lukas Eder Jul 25 '16 at 11:57
  • A solution involving reflection was provided a month earlier in May 2014 in [this blog comment](https://rmannibucau.wordpress.com/2014/03/27/java-8-default-interface-methods-and-jdk-dynamic-proxies/#comment-1333) and is applied in [the Java8 support extension](https://github.com/lviggiano/owner/blob/master/owner-java8/src/main/java/org/aeonbits/owner/util/Java8SupportImpl.java) of the OWNER API as well – krevelen Oct 03 '16 at 19:00
  • java.lang.IllegalAccessException: no private access for invokespecial – iirekm Jan 31 '17 at 15:00
  • the proper solution is here: https://zeroturnaround.com/rebellabs/recognize-and-conquer-java-proxies-default-methods-and-method-handles/ – iirekm Jan 31 '17 at 15:09
  • 1
    In Java 16+, this no longer works due to JEP 396. – kriegaex Dec 28 '21 at 01:17
10

In Java 16 (from the documentation, which also has more complex examples):

Object proxy = Proxy.newProxyInstance(loader, new Class[] { A.class },
        (o, m, params) -> {
            if (m.isDefault()) {
                // if it's a default method, invoke it
                return InvocationHandler.invokeDefault(o, m, params);
            }
        });
}
John
  • 251
  • 3
  • 5
  • Thanks - this really helped me to rescue my project vision. See https://github.com/m-m-m/bean/issues/4 for further details. – Jörg Jan 07 '22 at 18:37
2

If all you have is an interface, and all you have access to is a class object is an interface that extends your base interface, and you want to call the default method without a real instance of a class that implements the interface, you can:

Object target = Proxy.newProxyInstance(classLoader,
      new Class[]{exampleInterface}, (Object p, Method m, Object[] a) -> null);

Create an instance of the interface, and then construct the MethodHandles.Lookup using reflection:

Constructor<MethodHandles.Lookup> lookupConstructor = 
    MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE);
if (!lookupConstructor.isAccessible()) {
    lookupConstructor.setAccessible(true);
}

And then use that lookupConstructor to create a new instance of your interface that will allow private access to invokespecial. Then invoke the method on the fake proxy target you made earlier.

lookupConstructor.newInstance(exampleInterface,
        MethodHandles.Lookup.PRIVATE)
        .unreflectSpecial(method, declaringClass)
        .bindTo(target)
        .invokeWithArguments(args);
junkgui
  • 179
  • 2
  • 7
2

T. Neidhart answer almost worked but I got the java.lang.IllegalAccessException: no private access for invokespecial

Changing to use MethodHandles.privateLookup() solved it

return MethodHandles.privateLookupIn(clazz,MethodHandles.lookup())
                        .in(clazz)
                        .unreflectSpecial(method, clazz)
                        .bindTo(proxy)
                        .invokeWithArguments(args);

Here's a full example, the idea is that a user that extends a provided IMap can access nested nested map's with he's custom interface

interface IMap {
    Object get(String key);

    default <T> T getAsAny(String key){
        return (T)get(key);
    }


    default <T extends IMap> T getNestedAs(String key, Class<T> clazz) {
        Map<String,Object> nested = getAsAny(key);
        return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz},  (proxy, method, args) -> {
                    if (method.getName().equals("get")){
                        return nested.get(args[0]);
                    }
                    return MethodHandles.privateLookupIn(clazz, MethodHandles.lookup())
                            .in(clazz)
                            .unreflectSpecial(method, clazz)
                            .bindTo(proxy)
                            .invokeWithArguments(args);
                }
        );
    }
}

interface IMyMap extends IMap{

    default Integer getAsInt(String key){
        return getAsAny(key);
    }
    default IMyMap getNested(String key){
        return getNestedAs(key,IMyMap.class);
    }
}

@Test
public void test(){
    var data =Map.of("strKey","strValue", "nstKey", Map.of("intKey",42));
    IMyMap base = data::get;

    IMyMap myMap = base.getNested("nstKey");
    System.out.println( myMap.getAsInt("intKey"));
}
David Lilljegren
  • 1,799
  • 16
  • 19
1

Use:

Object result = MethodHandles.lookup()
    .in(method.getDeclaringClass())
    .unreflectSpecial(method, method.getDeclaringClass())
    .bindTo(target)
    .invokeWithArguments();
Krzysztof Krasoń
  • 26,515
  • 16
  • 89
  • 115
  • 6
    I tried that, gave me `Caused by: java.lang.IllegalAccessException: no private access for invokespecial: interface example.IExample, from example.IExample/package` – Gerald Mücke Jun 14 '16 at 12:52
1

We can see how spring process default method.

  1. try invoke public method MethodHandles.privateLookupIn(Class,Lookup) first. This should success on jdk9+.
  2. try create a Lookup with package private constructor MethodHandles.Lookup(Class).
  3. fallback to MethodHandles.lookup().findSpecial(...)

https://github.com/spring-projects/spring-data-commons/blob/2.1.8.RELEASE/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java

martian
  • 519
  • 1
  • 6
  • 16
  • spring does not support Java modules at all. Therefore the error `IllegalAccessException: no private access for invokespecial: interface example.ExampleMixin, from example.ExampleMixin/package` will not go away when springs "solution" is used. – Jörg Jan 07 '22 at 18:40
0

Lukas' answer works on Android 8+ (earlier releases do not have default methods) but relies on a private API that was blocked in later Android releases. Fortunately, the alternative constructor also works and is in grey list (unsupported) for now. The example (written in Kotlin) can be seen here.

@get:RequiresApi(26)
private val newLookup by lazy @TargetApi(26) {
    MethodHandles.Lookup::class.java.getDeclaredConstructor(Class::class.java, Int::class.java).apply {
        isAccessible = true
    }
}

@RequiresApi(26)
fun InvocationHandler.invokeDefault(proxy: Any, method: Method, vararg args: Any?) =
    newLookup.newInstance(method.declaringClass, 0xf)   // ALL_MODES
        .unreflectSpecial(method, method.declaringClass)
        .bindTo(proxy)
        .invokeWithArguments(*args)
Mygod
  • 2,077
  • 1
  • 19
  • 43