Java 如何从动态代理显式调用默认方法?

Java 如何从动态代理显式调用默认方法?,java,reflection,java-8,default-method,Java,Reflection,Java 8,Default Method,因为Java8接口可以有默认方法。 我知道如何从实现方法显式调用该方法,即。 (见附件) 但是如何使用反射(例如在代理上)显式调用默认方法 例如: interface ExampleMixin { String getText(); default void printInfo(){ System.out.println(getText()); } } class Example { public static void main(String... args)

因为Java8接口可以有默认方法。 我知道如何从实现方法显式调用该方法,即。 (见附件)

但是如何使用反射(例如在代理上)显式调用默认方法

例如:

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();
  }
}
使用:


如果您使用一个具体的impl类作为查找类和invokeSpecial的调用者,那么它应该正确调用接口的默认实现(不需要针对私有访问的hack):

当然,只有当您有一个对实现接口的具体对象的引用时,这才有效

编辑:只有当所讨论的类(上面代码中的示例)可以从调用方代码(例如匿名内部类)进行私有访问时,此解决方案才有效


MethodHandles/Lookup类的当前实现将不允许对任何不可从当前调用方类进行私有访问的类调用invokeSpecial。有各种可行的解决方法,但所有这些方法都需要使用反射来访问构造函数/方法,如果安装了SecurityManager,这可能会失败。

如果您只拥有一个接口,而您只拥有一个类对象,则只拥有一个扩展基本接口的接口,如果要在没有实现接口的类的实际实例的情况下调用默认方法,可以:

Object target = Proxy.newProxyInstance(classLoader,
      new Class[]{exampleInterface}, (Object p, Method m, Object[] a) -> null);
创建接口实例,然后构造MethodHandles。使用反射进行查找:

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

在JDK 8-10中使用
MethodHandle.Lookup
时,我也遇到过类似的问题,它们的行为有所不同

这种方法适用于Java8 在Java 8中,理想的方法是使用一个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(args);
                return null;
            }
        );

        duck.quack();
    }
}
解决方案
只需实现上述两种解决方案,并检查代码是否在JDK 8或更高版本的JDK上运行,就可以了。除非您不是:)

我们可以看到spring如何处理默认方法

  • 首先尝试调用公共方法
    MethodHandles.privateLookupIn(类,查找)
    。这将在jdk9+上成功
  • 尝试使用包私有构造函数
    MethodHandles.Lookup(Class)
    创建查找
  • 回退到MethodHandles.lookup().findSpecial(…)

  • T.Neidhart的答案几乎奏效了,但我得到了java.lang.IllegalAccessException:invokespecial没有私有访问权限

    改为使用MethodHandles.privateLookup()解决了这个问题

    return MethodHandles.privateLookupIn(clazz,MethodHandles.lookup())
                            .in(clazz)
                            .unreflectSpecial(method, clazz)
                            .bindTo(proxy)
                            .invokeWithArguments(args);
    
    下面是一个完整的示例,其思想是,扩展提供的IMap的用户可以使用其自定义界面访问嵌套的映射

    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"));
    }
    
    接口IMap{
    对象获取(字符串键);
    默认T getAsAny(字符串键){
    return(T)get(key);
    }
    默认T getNestedAs(字符串键,类clazz){
    映射嵌套=getAsAny(键);
    return(T)Proxy.newProxyInstance(this.getClass().getClassLoader(),newClass[]{clazz},(代理,方法,参数)->{
    if(method.getName().equals(“get”)){
    返回nested.get(args[0]);
    }
    返回MethodHandles.privateLookupIn(clazz,MethodHandles.lookup())
    .in(clazz)
    .未反映的特殊(方法、类别)
    .bindTo(代理)
    .调用参数(args);
    }
    );
    }
    }
    接口IMyMap扩展了IMap{
    默认整数getAsInt(字符串键){
    返回getAsAny(键);
    }
    默认IMyMap getNested(字符串键){
    返回getNestedAs(key,IMyMap.class);
    }
    }
    @试验
    公开无效测试(){
    var数据=Map.of(“strKey”,“strValue”,“nstKey”,Map.of(“intKey”,42));
    IMyMap base=data::get;
    IMyMap myMap=base.getNested(“nstKey”);
    System.out.println(myMap.getAsInt(“intKey”);
    }
    Java 16中的
    (来自,还有更复杂的示例):

    objectproxy=proxy.newProxyInstance(加载器,新类[]{A.Class},
    (o、m、params)->{
    if(m.isDefault()){
    //如果它是默认方法,则调用它
    返回InvocationHandler.invokeDefault(o,m,params);
    }
    });
    }
    
    我试过了,给了我
    原因:java.lang.IllegalAccessException:invoke没有私有访问权特别是:interface-example.IExample,来自example.IExample/package
    这不是重复的“我正在寻找一种“官方”的方式来做这件事”我可能弄错了,但我担心如果您的子类型覆盖了超类型,您就不能从超类型调用该方法。假设您的超类型有
    acceptSquare
    方法可以接受任何方块,但是您的子类型专门处理红色方块,因此它相应地覆盖它以添加颜色测试(之后它调用
    super.addSquare
    )。所以,允许某人从这种方法的超类型(甚至通过反射)从外部实现调用可能是一个很大的安全漏洞?i、 我有一个实例,希望通过在运行时向实例“添加”带有默认方法的接口来添加其他功能。必须有一种方法,我相应地更新了示例,首先希望它尽可能简单,但希望我的意图现在变得更清楚
    默认
    方法的调用目标必须是该
    接口
    的实例。在您的示例代码中,它是一个
    对象
    -应该如何工作?嗯,这也适用于匿名类。。。但是,鉴于我以前不知道接口(即,因为它是一个参数),我可以动态创建匿名类的实例吗?(代理似乎不起作用,字节码生成也不是一个选项)本例中的
    ex
    是什么?这里它必须是
    Example
    的一个实例,那么为什么不仅仅是u
    lookupConstructor.newInstance(exampleInterface,
            MethodHandles.Lookup.PRIVATE)
            .unreflectSpecial(method, declaringClass)
            .bindTo(target)
            .invokeWithArguments(args);
    
    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();
        }
    }
    
    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();
        }
    }
    
    return MethodHandles.privateLookupIn(clazz,MethodHandles.lookup())
                            .in(clazz)
                            .unreflectSpecial(method, clazz)
                            .bindTo(proxy)
                            .invokeWithArguments(args);
    
    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"));
    }