Nota del traductor: El desarrollo del marco CUBA genera una gran cantidad de proyectos de I + D. En el curso de uno de estos proyectos, result贸 que necesitamos llamar a los m茅todos de interfaz predeterminados desde las clases proxy. Nos topamos con un art铆culo muy 煤til, me parece que la experiencia presentada en 茅l ser谩 al menos interesante y, como mucho, 煤til para un amplio c铆rculo de desarrolladores.Cuando se trata de acceder a los m茅todos de interfaz predeterminados en Java a trav茅s de la reflexi贸n, google no ayuda mucho. Por ejemplo, una
soluci贸n en StackOverflow solo funciona en ciertas situaciones y no en todas las versiones de Java.
Este art铆culo discutir谩 varios enfoques para llamar a m茅todos de interfaz predeterminados a trav茅s de la reflexi贸n, esto puede ser necesario, por ejemplo, al crear clases proxy.
TL; DR Si no puede esperar, todos los m茅todos para llamar a los m茅todos predeterminados descritos en este art铆culo est谩n disponibles
en este enlace , y este problema ya se ha resuelto en nuestra biblioteca
jOOR .
Interfaces de proxy con m茅todos predeterminados
La 煤til API java.lang.reflect.Proxy ha existido durante mucho tiempo, con ella podemos hacer cosas geniales, por ejemplo:
import java.lang.reflect.Proxy; public class ProxyDemo { interface Duck { void quack(); } public static void main(String[] a) { Duck duck = (Duck) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class[] { Duck.class }, (proxy, method, args) -> { System.out.println("Quack"); return null; } ); duck.quack(); } }
Este c贸digo simplemente genera:
Quack
En este ejemplo, creamos una instancia de un proxy que implementa la API de Duck utilizando el
InvocationHandler , que es b谩sicamente un lambda llamado para cada m茅todo de la interfaz de Duck.
La parte interesante comenzar谩 cuando queramos agregar una implementaci贸n de m茅todo a la interfaz y delegar una llamada a este m茅todo:
interface Duck { default void quack() { System.out.println("Quack"); } }
Lo m谩s probable es que desee escribir este c贸digo:
import java.lang.reflect.Proxy; public class ProxyDemo { interface Duck { default void quack() { System.out.println("Quack"); } } public static void main(String[] a) { Duck duck = (Duck) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class[] { Duck.class }, (proxy, method, args) -> { method.invoke(proxy); return null; } ); duck.quack(); } }
Pero esto solo generar谩 una larga pila de excepciones anidadas (y esto no est谩 relacionado con la llamada a la implementaci贸n del m茅todo en la interfaz, simplemente est谩 prohibido hacerlo):
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException at $Proxy0.quack(Unknown Source) at ProxyDemo.main(ProxyDemo.java:20) Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at ProxyDemo.lambda$0(ProxyDemo.java:15) ... 2 more Caused by: java.lang.reflect.UndeclaredThrowableException at $Proxy0.quack(Unknown Source) ... 7 more Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at ProxyDemo.lambda$0(ProxyDemo.java:15) ... 8 more Caused by: java.lang.reflect.UndeclaredThrowableException at $Proxy0.quack(Unknown Source) ... 13 more Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at ProxyDemo.lambda$0(ProxyDemo.java:15) ... 14 more Caused by: java.lang.reflect.UndeclaredThrowableException at $Proxy0.quack(Unknown Source) ... 19 more ... ... ... goes on forever
No muy 煤til
Uso de API de manejadores de m茅todos
Entonces, una b煤squeda en Google nos dice que
debemos usar la API MethodHandles . Bueno, vamos a intentarlo!
import java.lang.invoke.MethodHandles; import java.lang.reflect.Proxy; public class ProxyDemo { interface Duck { default void quack() { System.out.println("Quack"); } } public static void main(String[] a) { Duck duck = (Duck) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class[] { Duck.class }, (proxy, method, args) -> { MethodHandles .lookup() .in(Duck.class) .unreflectSpecial(method, Duck.class) .bindTo(proxy) .invokeWithArguments(); return null; } ); duck.quack(); } }
隆Genial, parece funcionar!
Quack
... pero no.
Llamar a un m茅todo de interfaz con acceso no privado
La interfaz del ejemplo anterior se realiz贸 cuidadosamente para que el c贸digo de llamada tuviera acceso privado, es decir La interfaz estaba anidada en la clase de llamada. Pero, 驴qu茅 pasa si tenemos una interfaz no anidada?
import java.lang.invoke.MethodHandles; 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() .in(Duck.class) .unreflectSpecial(method, Duck.class) .bindTo(proxy) .invokeWithArguments(); return null; } ); duck.quack(); } }
Casi el mismo c贸digo ya no funciona. Obtenga la IllegalAccessException:
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException at $Proxy0.quack(Unknown Source) at ProxyDemo.main(ProxyDemo.java:26) Caused by: java.lang.IllegalAccessException: no private access for invokespecial: interface Duck, from Duck/package at java.lang.invoke.MemberName.makeAccessException(MemberName.java:850) at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1572) at java.lang.invoke.MethodHandles$Lookup.unreflectSpecial(MethodHandles.java:1231) at ProxyDemo.lambda$0(ProxyDemo.java:19) ... 2 more
La mierda sali贸. Si todav铆a lo busca en Google, puede encontrar la siguiente soluci贸n, que accede a las
partes internas de
MethodHandles .
Busque a trav茅s de la reflexi贸n.
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(); } }
Y, salud, obtenemos:
Quack
Logramos hacerlo en JDK 8. 驴Qu茅 tal JDK 9 o 10?
WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by ProxyDemo (file:/C:/Users/lukas/workspace/playground/target/classes/) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class) WARNING: Please consider reporting this to the maintainers of ProxyDemo WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release Quack
Cubrezapatos. Esto es lo que sucede
por defecto . Si ejecutamos el programa con la bandera
--illegal-access=deny
:
java --illegal-access=deny ProxyDemo
Bueno, entonces obtenemos (隆y con raz贸n!):
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make java.lang.invoke.MethodHandles$Lookup(java.lang.Class) accessible: module java.base does not "opens java.lang.invoke" to unnamed module @357246de at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281) at java.base/java.lang.reflect.Constructor.checkCanSetAccessible(Constructor.java:192) at java.base/java.lang.reflect.Constructor.setAccessible(Constructor.java:185) at ProxyDemo.lambda$0(ProxyDemo.java:18) at $Proxy0.quack(Unknown Source) at ProxyDemo.main(ProxyDemo.java:28)
Uno de los objetivos del proyecto Jigsaw era precisamente evitar tales ataques. Entonces, 驴qu茅 soluci贸n es mejor? Es?
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(); } }
Quack
Genial, esto funciona en Java 9 y 10, pero 驴qu茅 pasa con Java 8?
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException at $Proxy0.quack(Unknown Source) at ProxyDemo.main(ProxyDemo.java:25) Caused by: java.lang.IllegalAccessException: no private access for invokespecial: interface Duck, from ProxyDemo at java.lang.invoke.MemberName.makeAccessException(MemberName.java:850) at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1572) at java.lang.invoke.MethodHandles$Lookup.findSpecial(MethodHandles.java:1002) at ProxyDemo.lambda$0(ProxyDemo.java:18) ... 2 more
驴Me est谩s tomando el pelo?
Entonces, tenemos una soluci贸n (hack) que funciona en Java 8, pero no en 9 y 10, y hay una soluci贸n que funciona en 9 y 10, pero no en 8
Investigaci贸n m谩s profunda
Bueno, acabo de intentar ejecutar un c贸digo diferente en diferentes JDK. La siguiente clase intenta todas las combinaciones anteriores. Tambi茅n est谩 disponible como GIST
aqu铆 .
Compile el c贸digo usando JDK 9 o 10 (porque se requiere la API JDK 9+:
MethodHandles.privateLookupIn () ), pero necesita compilarlo
usando el comando a continuaci贸n para ejecutar la clase en JDK 8:
javac -source 1.8 -target 1.8 CallDefaultMethodThroughReflection.java
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface PrivateInaccessible { default void quack() { System.out.println(" -> PrivateInaccessible.quack()"); } } public class CallDefaultMethodThroughReflection { interface PrivateAccessible { default void quack() { System.out.println(" -> PrivateAccessible.quack()"); } } public static void main(String[] args) { System.out.println("PrivateAccessible"); System.out.println("-----------------"); System.out.println(); proxy(PrivateAccessible.class).quack(); System.out.println(); System.out.println("PrivateInaccessible"); System.out.println("-------------------"); System.out.println(); proxy(PrivateInaccessible.class).quack(); } private static void quack(Lookup lookup, Class<?> type, Object proxy) { System.out.println("Lookup.in(type).unreflectSpecial(...)"); try { lookup.in(type) .unreflectSpecial(type.getMethod("quack"), type) .bindTo(proxy) .invokeWithArguments(); } catch (Throwable e) { System.out.println(" -> " + e.getClass() + ": " + e.getMessage()); } System.out.println("Lookup.findSpecial(...)"); try { lookup.findSpecial(type, "quack", MethodType.methodType(void.class, new Class[0]), type) .bindTo(proxy) .invokeWithArguments(); } catch (Throwable e) { System.out.println(" -> " + e.getClass() + ": " + e.getMessage()); } } @SuppressWarnings("unchecked") private static <T> T proxy(Class<T> type) { return (T) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class[] { type }, (Object proxy, Method method, Object[] arguments) -> { System.out.println("MethodHandles.lookup()"); quack(MethodHandles.lookup(), type, proxy); try { System.out.println(); System.out.println("Lookup(Class)"); Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class); constructor.setAccessible(true); constructor.newInstance(type); quack(constructor.newInstance(type), type, proxy); } catch (Exception e) { System.out.println(" -> " + e.getClass() + ": " + e.getMessage()); } try { System.out.println(); System.out.println("MethodHandles.privateLookupIn()"); quack(MethodHandles.privateLookupIn(type, MethodHandles.lookup()), type, proxy); } catch (Error e) { System.out.println(" -> " + e.getClass() + ": " + e.getMessage()); } return null; } ); } }
La salida del programa anterior:
Java 8 $ java -version java version "1.8.0_141" Java(TM) SE Runtime Environment (build 1.8.0_141-b15) Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) $ java CallDefaultMethodThroughReflection PrivateAccessible ----------------- MethodHandles.lookup() Lookup.in(type).unreflectSpecial(...) -> PrivateAccessible.quack() Lookup.findSpecial(...) -> class java.lang.IllegalAccessException: no private access for invokespecial: interface CallDefaultMethodThroughReflection$PrivateAccessible, from CallDefaultMethodThroughReflection Lookup(Class) Lookup.in(type).unreflectSpecial(...) -> PrivateAccessible.quack() Lookup.findSpecial(...) -> PrivateAccessible.quack() MethodHandles.privateLookupIn() -> class java.lang.NoSuchMethodError: java.lang.invoke.MethodHandles.privateLookupIn(Ljava/lang/Class;Ljava/lang/invoke/MethodHandles$Lookup;)Ljava/lang/invoke/MethodHandles$Lookup; PrivateInaccessible ------------------- MethodHandles.lookup() Lookup.in(type).unreflectSpecial(...) -> class java.lang.IllegalAccessException: no private access for invokespecial: interface PrivateInaccessible, from PrivateInaccessible/package Lookup.findSpecial(...) -> class java.lang.IllegalAccessException: no private access for invokespecial: interface PrivateInaccessible, from CallDefaultMethodThroughReflection Lookup(Class) Lookup.in(type).unreflectSpecial(...) -> PrivateInaccessible.quack() Lookup.findSpecial(...) -> PrivateInaccessible.quack() MethodHandles.privateLookupIn() -> class java.lang.NoSuchMethodError: java.lang.invoke.MethodHandles.privateLookupIn(Ljava/lang/Class;Ljava/lang/invoke/MethodHandles$Lookup;)Ljava/lang/invoke/MethodHandles$Lookup;
Java 9 $ java -version java version "9.0.4" Java(TM) SE Runtime Environment (build 9.0.4+11) Java HotSpot(TM) 64-Bit Server VM (build 9.0.4+11, mixed mode) $ java --illegal-access=deny CallDefaultMethodThroughReflection PrivateAccessible ----------------- MethodHandles.lookup() Lookup.in(type).unreflectSpecial(...) -> PrivateAccessible.quack() Lookup.findSpecial(...) -> PrivateAccessible.quack() Lookup(Class) -> class java.lang.reflect.InaccessibleObjectException: Unable to make java.lang.invoke.MethodHandles$Lookup(java.lang.Class) accessible: module java.base does not "opens java.lang.invoke" to unnamed module @30c7da1e MethodHandles.privateLookupIn() Lookup.in(type).unreflectSpecial(...) -> PrivateAccessible.quack() Lookup.findSpecial(...) -> PrivateAccessible.quack() PrivateInaccessible ------------------- MethodHandles.lookup() Lookup.in(type).unreflectSpecial(...) -> class java.lang.IllegalAccessException: no private access for invokespecial: interface PrivateInaccessible, from PrivateInaccessible/package (unnamed module @30c7da1e) Lookup.findSpecial(...) -> PrivateInaccessible.quack() Lookup(Class) -> class java.lang.reflect.InaccessibleObjectException: Unable to make java.lang.invoke.MethodHandles$Lookup(java.lang.Class) accessible: module java.base does not "opens java.lang.invoke" to unnamed module @30c7da1e MethodHandles.privateLookupIn() Lookup.in(type).unreflectSpecial(...) -> PrivateInaccessible.quack() Lookup.findSpecial(...) -> PrivateInaccessible.quack()
Java 10 $ java -version java version "10" 2018-03-20 Java(TM) SE Runtime Environment 18.3 (build 10+46) Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10+46, mixed mode) $ java --illegal-access=deny CallDefaultMethodThroughReflection ... , Java 9
Conclusi贸n
Comprender todo esto es un poco complicado.
- En Java 8, el mejor enfoque de trabajo es un hack que interrumpe las partes internas de JDK a trav茅s del acceso al constructor privado de paquetes de la clase
Lookup
. Esta es la 煤nica forma de invocar m茅todos de interfaz de manera coherente con acceso privado y acceso no privado desde cualquier clase. - En Java 9 y 10, la mejor manera es usar
Lookup.findSpecial()
(no funciona en Java 8) o MethodHandles.privateLookupIn()
(el m茅todo no existe en Java 8). El 煤ltimo enfoque debe usarse si la interfaz est谩 en otro m贸dulo. Este m贸dulo debe proporcionar una interfaz para llamadas externas.
Honestamente, esto es un poco confuso. Meme adecuado para esto:

Rafael Winterhalter (autor de ByteBuddy) dijo que la soluci贸n "real" estar谩 en la versi贸n revisada de la API Proxy:

Traducci贸nLukas Eder : "驴No sabes la raz贸n por la que decidiste no permitirlo m谩s? 驴O simplemente te lo perdiste (muy probablemente no)?
Rafael Winterhalter : 鈥淣o hay raz贸n. Este es un efecto secundario del modelo de seguridad de Java para la clase de b煤squeda de MethodHandle. Idealmente, las interfaces proxy deber铆an tener dicha b煤squeda provista como argumento ( constructor - aprox. Por. ), Pero no consideraron esto. Sin 茅xito, ofrec铆 una extensi贸n similar para la API de transformaci贸n de archivos de clase ".
No estoy seguro de que esto resuelva todos los problemas, pero realmente debe asegurarse de que el desarrollador no se preocupe por todo lo anterior.
Y est谩 claro que este art铆culo no est谩 completo, por ejemplo, no se prob贸 si estos enfoques funcionar铆an si Duck se importara de otro m贸dulo:

Traducci贸nJOOQ : t铆tulo y enlace del art铆culo
Rafael Winterhalter : 鈥溌縃as intentado poner a Duck en un m贸dulo que exporta pero no abre un paquete de interfaz? Apuesto a que su soluci贸n para Java 9+ no funcionar谩 si utiliza la ruta del m贸dulo ".
... y ese ser谩 el tema del pr贸ximo art铆culo.
Usando jOOR
Si usa jOOR (nuestra biblioteca para la API de reflexi贸n, est谩
aqu铆 ), la versi贸n 0.9.8 incluir谩 una soluci贸n para esto:
github.com/jOOQ/jOOR/issues/49Fix simplemente utiliza el enfoque Reflection API hack en Java 8 o MethodHandles.privateLookupIn () para Java 9+. Puedes escribir:
Reflect.on(new Object()).as(PrivateAccessible.class).quack(); Reflect.on(new Object()).as(PrivateInaccessible.class).quack();