【问题标题】:How do I invoke Java 8 default methods reflectively如何反射地调用 Java 8 默认方法
【发布时间】:2014-05-02 03:00:12
【问题描述】:

鉴于这个简单的“Hello World”ish Java 8 接口,我如何通过反射调用它的 hello() 方法?

public interface Hello {
    default String hello() {
        return "Hello";
    }
}

【问题讨论】:

标签: java reflection interface java-8


【解决方案1】:

非常感谢卢卡斯。这是他对 Java 8 vs 9+ 检查和对非空返回和参数的支持的回答。一定要给他的答案一个赞成票。

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;

public class ThanksLukas implements InvocationHandler {
    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {

        if (method.isDefault()) {
            final float version = Float.parseFloat(System.getProperty("java.class.version"));
            if (version <= 52) {
                final Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class);
                constructor.setAccessible(true);

                final Class<?> clazz = method.getDeclaringClass();
                return constructor.newInstance(clazz)
                        .in(clazz)
                        .unreflectSpecial(method, clazz)
                        .bindTo(proxy)
                        .invokeWithArguments(args);
            } else {
                return MethodHandles.lookup()
                        .findSpecial(
                                method.getDeclaringClass(),
                                method.getName(),
                                MethodType.methodType(method.getReturnType(), new Class[0]),
                                method.getDeclaringClass()
                        ).bindTo(proxy)
                        .invokeWithArguments(args);
            }
        }

        // your regular proxy fun here

【讨论】:

    【解决方案2】:

    不幸的是,似乎没有一个理想的解决方案适用于所有表现不同的 JDK 8、9、10。 I've run into issues when fixing an issue in jOORI've also blogged about the correct solution here in detail.

    这种方法适用于 Java 8

    在 Java 8 中,理想的方法是使用从 Lookup 访问包私有构造函数的 hack:

    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();
                    return null;
                }
            );
    
            duck.quack();
        }
    }
    

    这是唯一适用于私有可访问和私有不可访问接口的方法。但是,上述方法对 JDK 内部进行了非法反射访问,这在未来的 JDK 版本中将不再有效,或者如果在 JVM 上指定了--illegal-access=deny

    这种方法适用于 Java 9 和 10,但不适用于 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();
                    return null;
                }
            );
    
            duck.quack();
        }
    }
    

    解决方案

    只需实现上述两个解决方案,并检查您的代码是在 JDK 8 还是更高版本的 JDK 上运行,就可以了。直到你不是:)

    【讨论】:

    • 我不认为这个解决方案是完整的,对于一般情况下,类引用 (Duck.class) 作为参数传入。该类可能是一个具体类,它从它实现的接口(或从超类)继承默认方法,或从从超接口继承默认方法的接口,或从从超级界面。您必须遍历所有超类,收集唯一实现的接口,然后找到超接口的传递闭包,然后尝试每一个。
    • @LukeHutchison。用例是传递包含默认方法本身的接口。这就是OP想要的。我不确定你在找什么。
    【解决方案3】:

    您可以为此使用MethodHandles

    import java.lang.invoke.MethodHandles;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class ReflectiveDefaultMethodCallExample {
    
        static interface Hello {
            default String hello() {
                return "Hello";
            }
        }
    
        public static void main(String[] args) throws Throwable{
    
            Hello target =
                    //new Hello(){};
                    (Hello)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{Hello.class}, (Object proxy, Method method, Object[] arguments) -> null);
            Method method = Hello.class.getMethod("hello");
    
            Object result = MethodHandles.lookup()
                                         .in(method.getDeclaringClass())
                                         .unreflectSpecial(method,method.getDeclaringClass())
                                         .bindTo(target)
                                         .invokeWithArguments();
            System.out.println(result); //Hello
        }
    }
    

    【讨论】:

    • 好主意,因为它提示了InvocationHandler 如何委托给默认方法。这在其他场景中可能非常有用。
    • 这是正确的,除了在用于查找句柄的文件之外的文件中定义的任何接口都会失败。我使用了以下修改: final Field field = Lookup.class.getDeclaredField("IMPL_LOOKUP"); field.setAccessible(true);最终查找查找 = (查找) field.get(null);最终对象值 = 查找 .unreflectSpecial(method, method.getDeclaringClass()) .bindTo(t) .invokeWithArguments();
    • 本文的 cmets 也有其他解决方法rmannibucau.wordpress.com/2014/03/27/… 最好的可能是获取 Lookup 类的构造函数,这样您就可以创建具有 PRIVATE 访问权限的查找,而不是访问全权限 IMPL_LOOKUP就像我上面做的那样。
    • 澄清一下... MethodHandles 对谁可以访问哪些方法有反射保护,因此您需要从 Lookup 类中获取通用 Lookup 对象,而不是使用 MethodHandles.lookup 提供的对象跨度>
    【解决方案4】:

    我已经found a solution 使用来自sun.misc.ProxyGenerator 的代码反射性地从上述接口创建实例,该代码通过组装字节码定义了一个类HelloImpl。现在我可以写了:

    Class<?> clazz = Class.forName("Hello");
    Object instance;
    
    if (clazz.isInterface()) {
        instance = new InterfaceInstance(clazz).defineClass().newInstance();
    } else {
        instance = clazz.newInstance();
    }
    
    return clazz.getMethod("hello").invoke(instance);
    

    ...但这很丑。

    【讨论】:

    • 那一定是丑陋的,否则有人会想用它!
    • 肯定比必要的工作多。访问可以通过 MethodHandles 完成,不需要大量依赖或过多的运行时工作。
    【解决方案5】:

    您不能直接调用它,因为您需要一个实现类的实例。为此,您需要一个实现类。 default 方法不是 static 方法,也不能创建接口的实例。

    所以,假设你有一个实现类:

    class HelloImpl implements Hello {  }
    

    你可以像这样调用方法:

    Class<HelloImpl> clazz = HelloImpl.class;
    Method method = clazz.getMethod("hello");
    System.out.println(method.invoke(new HelloImpl()));  // Prints "Hello"
    

    【讨论】:

    • 我应该更准确地回答我的问题:我如何实例化它并调用 ist hello() 方法 - both 通过反射?
    • @thekid 您无法实例化接口。您需要一个实现类,我已在答案中显示。
    猜你喜欢
    • 2016-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多