34

Java 8 インターフェイスにはデフォルトのメソッドがある可能性があるためです。実装メソッドからメソッドを明示的に呼び出す方法を知っています。つまり、Java でデフォルト メソッドを明示的に呼び出す方法を参照してください。

しかし、たとえばプロキシでリフレクションを使用してデフォルト メソッドを明示的に呼び出すにはどうすればよいでしょうか。

例:

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

編集: How do I invoke Java 8 default methods refletivelyで同様の質問がされていることは知っていますが、これは2つの理由で私の問題を解決していません:

  • その質問で説明されている問題は、一般的にリフレクションを介して呼び出す方法を目的としているため、デフォルトのメソッドとオーバーライドされたメソッドの区別はありませんでした。これは簡単で、インスタンスのみが必要です。
  • 答えの1つ-メソッドハンドルを使用-は、アクセス修飾子をルックアップクラスのフィールドに変更するなどの厄介なハック(imho)でのみ機能します。これは、次のような「ソリューション」の同じカテゴリです: Javaリフレクションを使用してプライベート静的最終フィールドを変更します:それが可能であることを知っているのは良いことですが、私はそれを本番環境では使用しません.それを行うための「公式」の方法を探しています.

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

8 に答える 8

8

Java 16 の場合 (より複雑な例もあるドキュメントから):

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);
            }
        });
}
于 2021-04-01T22:42:01.867 に答える
2

インターフェイスしかなく、アクセスできるのはクラス オブジェクトだけであり、基本インターフェイスを拡張するインターフェイスであり、インターフェイスを実装するクラスの実際のインスタンスなしで既定のメソッドを呼び出したい場合は、次のことができます。

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

インターフェイスのインスタンスを作成し、リフレクションを使用して MethodHandles.Lookup を作成します。

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

そして、それを使用して、lookupConstructorへのプライベート アクセスを許可するインターフェイスの新しいインスタンスを作成しますinvokespecialtarget次に、先ほど作成した偽のプロキシでメソッドを呼び出します。

lookupConstructor.newInstance(exampleInterface,
        MethodHandles.Lookup.PRIVATE)
        .unreflectSpecial(method, declaringClass)
        .bindTo(target)
        .invokeWithArguments(args);
于 2017-02-25T17:18:01.420 に答える
1

スプリングがデフォルトのメソッドをどのように処理するかを見ることができます。

  1. 最初にpublic メソッドを呼び出してみてくださいMethodHandles.privateLookupIn(Class,Lookup)。これは jdk9+ で成功するはずです。
  2. パッケージのプライベート コンストラクターでルックアップを作成してみてくださいMethodHandles.Lookup(Class)
  3. 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

于 2019-05-30T08:20:57.683 に答える
1

T. Neidhartの回答はほとんど機能しましたが、java.lang.IllegalAccessException: no private access for 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"));
}
于 2020-05-05T18:27:01.487 に答える
1

使用する:

Object result = MethodHandles.lookup()
    .in(method.getDeclaringClass())
    .unreflectSpecial(method, method.getDeclaringClass())
    .bindTo(target)
    .invokeWithArguments();
于 2016-06-14T12:48:34.450 に答える