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();
}
}
編集:同様の質問が どのように呼び出すかJava 8つのデフォルトメソッドrefletively 、しかしこれは2つの理由で私の問題を解決していません:
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の呼び出し元として使用する場合、インターフェイスのデフォルトの実装を正しく呼び出す必要があります(プライベートアクセスのハックは必要ありません)。
Example target = new Example();
...
Class targetClass = target.getClass();
return MethodHandles.lookup()
.in(targetClass)
.unreflectSpecial(method, targetClass)
.bindTo(target)
.invokeWithArguments();
もちろん、これは、インターフェイスを実装する具体的なオブジェクトへの参照がある場合にのみ機能します。
編集:このソリューションは、問題のクラス(上記のコードの例)が呼び出し元のコードからプライベートにアクセスできる場合にのみ機能します。匿名の内部クラス。
MethodHandles/Lookupクラスの現在の実装では、現在の呼び出し元クラスからプライベートにアクセスできないクラスでinvokeSpecialを呼び出すことはできません。利用可能なさまざまな回避策がありますが、それらはすべて、コンストラクター/メソッドにアクセスできるようにするためにリフレクションを使用する必要があります。これは、SecurityManagerがインストールされている場合はおそらく失敗します。
JDK8-10でMethodHandle.Lookup
を使用すると、動作が異なる同様の問題にも悩まされてきました。 ここで正しい解決策について詳しくブログに書いています 。
Java 8では、理想的なアプローチは、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();
return null;
}
);
duck.quack();
}
}
これは、プライベートアクセス可能なインターフェイスとプライベートアクセス不可能なインターフェイスの両方で機能する唯一のアプローチです。ただし、上記のアプローチでは、JDK内部への不正なリフレクティブアクセスが実行されます。これは、将来のJDKバージョンでは機能しなくなります。または、JVMで--illegal-access=deny
が指定されている場合も同様です。
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で実行されているかどうかを確認するだけで、問題はありません。あなたがいないまで:)
持っているのがインターフェースだけで、アクセスできるのがクラスオブジェクトだけで、基本インターフェースを拡張するインターフェースであり、インターフェースを実装するクラスの実際のインスタンスなしでデフォルトのメソッドを呼び出したい場合は、次のことができます。
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
を使用して、invokespecial
へのプライベートアクセスを許可するインターフェイスの新しいインスタンスを作成します。次に、前に作成した偽のプロキシtarget
でメソッドを呼び出します。
lookupConstructor.newInstance(exampleInterface,
MethodHandles.Lookup.PRIVATE)
.unreflectSpecial(method, declaringClass)
.bindTo(target)
.invokeWithArguments(args);
使用する:
Object result = MethodHandles.lookup()
.in(method.getDeclaringClass())
.unreflectSpecial(method, method.getDeclaringClass())
.bindTo(target)
.invokeWithArguments();
スプリングプロセスのデフォルトメソッドがどのようになっているのかがわかります。
MethodHandles.privateLookupIn(Class,Lookup)
を呼び出してみてください。これはjdk9 +で成功するはずです。MethodHandles.Lookup(Class)
を使用してルックアップを作成してみてください。