如何从动态代理显式调用默认方法? [英] How to explicitly invoke default method from a dynamic Proxy?

查看:238
本文介绍了如何从动态代理显式调用默认方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

由于Java 8接口可以使用默认方法。
我知道如何从实现方法中显式调用该方法,即
(参见在Java中显式调用默认方法

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?

示例:

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






修改:我知道在我如何反复调用Java 8默认方法,但这并没有解决我的问题有两个原因:


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:


  • 该问题中描述的问题针对如何通过反射一般调用它 - 所以没有区分default和overriden方法 - 这很简单,你只需要一个实例。

  • 其中一个答案 - 使用方法句柄 - 只能使用令人讨厌的黑客(imho),例如将访问修饰符更改为查找类的字段,这类似于解决方案,如下所示: a href =https://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection>使用Java反射更改私有静态最终字段: it's很高兴知道它可能,但我不会在生产中使用它 - 我正在寻找一种官方方式来实现它。

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

IllegalAccessException 抛出 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)


推荐答案

如果你使用具体的impl类作为lookupClass和invokeSpecial的调用者它应该正确调用接口的默认实现(没有hack需要私人访问):

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.

编辑:此解决方案仅在有问题的类(上面的代码中的示例)可以从调用者代码访问时才有效,例如一个匿名的内部类。

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.

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

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.

这篇关于如何从动态代理显式调用默认方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆