Contenido
Introducción
API de proxy dinámico
Serialización
Ejemplos
Introducción
Una clase de proxy dinámico es una clase que implementa una lista de interfaces especificada en tiempo de ejecución de modo que una invocación de método a través de una de las interfaces de una instancia de la clase se codifica y se envía a otro objeto a través de una interfaz uniforme. Por lo tanto, se puede usar una clase proxy dinámica para crear un objeto proxy seguro para un tipo para una lista de interfaces sin requerir la generación previa de la clase proxy, como con herramientas en tiempo de compilación.Las invocaciones de métodos en una instancia de una clase proxy dinámica se envían a un único método en el controlador de invocación de la instancia, y se codifican con un objetojava.lang.reflect.Method
que identifica el método invocado y una matriz de tipo Object
que contiene los argumentos.
Las clases de proxy dinámico son útiles para una aplicación o biblioteca que necesita proporcionar un envío reflexivo seguro de tipo de invocaciones en objetos que presentan API de interfaz. Por ejemplo, una aplicación puede usar una clase proxy dinámica para crear un objeto que implemente múltiples interfaces de escucha de eventos arbitrarios interfaces interfaces que amplíen java.util.EventListener
to para procesar una variedad de eventos de diferentes tipos de manera uniforme, como registrar todos estos eventos en un archivo.
API de clase de proxy dinámico
Una clase de proxy dinámico (conocida simplemente como proxyclass a continuación) es una clase que implementa una lista de interfaces especificadas en tiempo de ejecución cuando se crea la clase.
Una interfaz proxy es una interfaz que se implementa mediante una clase proxy.
Una instancia proxy es una instancia de un proxyclass.
Crear una clase Proxy
Las clases Proxy, así como las instancias de ellas, se crean utilizando los métodos estáticos de la clase java.lang.reflejar.Proxy.
El método Proxy.getProxyClass
devuelve el objetojava.lang.Class
para una clase proxy dada un cargador de clases y una matriz de interfaces. La clase proxy se definirá en el cargador de clases especificado e implementará todas las interfaces suministradas. Si ya se ha definido una clase proxy para la misma permutación de interfaces en el cargador de clases, se devolverá la clase proxy existente; de lo contrario, se generará dinámicamente una clase proxy para estas interfaces y se definirá en el cargador de clases.
Hay varias restricciones en los parámetros que pueden pasarse a Proxy.getProxyClass
:
- Todos los objetos
Class
de la matrizinterfaces
deben representar interfaces, clases de notas o tipos primitivos. - No hay dos elementos en el array
interfaces
que puedan referirse a objetosClass
idénticos. - Todos los tipos de interfaz deben ser visibles por nombre a través del cargador de clases especificado. En otras palabras, para cargador de clases
cl
y cada interfazi
, la siguiente expresión debe ser verdadera:Class.forName(i.getName(), false, cl) == i
- Todas las interfaces no públicas deben estar en el mismo paquete;de lo contrario, no sería posible que la clase proxy implementara todas las interfaces, independientemente del paquete en el que esté definida.
- Para cualquier conjunto de métodos de barras de las interfaces especificadas que tengan la misma firma:
- Si el tipo de retorno de cualquiera de los métodos es un tipo primitivo orvoid, entonces todos los métodos deben tener el mismo tipo de retorno.
- De lo contrario, uno de los métodos debe tener un tipo de retorno que se pueda asignar a todos los tipos de retorno del resto de los métodos.
- La clase proxy resultante no debe exceder los límites impuestos a las clases por la máquina virtual. Por ejemplo, la máquina virtual puede limitar el número de interfaces que una clase puede implementar a 65535; en ese caso, el tamaño de la matriz
interfaces
no debe superar 65535.
Si se viola alguna de estas restricciones,Proxy.getProxyClass
lanzará unIllegalArgumentException
. Si el argumento arrayinterfaces
o cualquiera de sus elementos sonnull
, a NullPointerException
será bethrown.
Tenga en cuenta que el orden de las interfaces proxy especificadas es significativo: dos solicitudes para una clase proxy con la misma combinación de interfaces pero en un orden diferente resultarán en dos clases proxy distintas. Las clases Proxy se distinguen por orden de sus interfaces proxy para proporcionar codificación de invocación de métodos determinísticos en casos en los que dos o más de las interfaces proxy comparten un método con el mismo nombre y la misma firma de parámetros; este razonamiento se describe con más detalle en la sección a continuación titulada Métodos duplicados en múltiples Interfaces Proxy.
Para que no sea necesario generar una nueva clase proxy cada vez que se invoque Proxy.getProxyClass
con el mismo cargador de clase y la lista de interfaces, la implementación de la API de clase proxy dinámica debe mantener una caché de proxyclasses generados, con claves de sus cargadores y lista de interfaces correspondientes.La implementación debe tener cuidado de no hacer referencia a los cargadores de clases, interfaces y clases proxy de tal manera que se evite que los cargadores de clases, y todas sus clases, se recopilen en la basura cuando sea apropiado.
Propiedades de clase proxy
Una clase proxy tiene las siguientes propiedades:
- Las clases proxy son públicas, finales y no abstractas.
- El nombre no calificado de una clase proxy no está especificado. Sin embargo,el espacio de nombres de clase que comienza con la cadena
"$Proxy"
debe reservarse para las clases proxy. - Una clase proxy se extiende
java.lang.reflect.Proxy
. - Una clase proxy implementa exactamente las interfaces de creación de atits especificadas, en el mismo orden.
- Si una clase proxy implementa una interfaz no pública, se definirá en el mismo paquete que esa interfaz. De lo contrario, el paquete de una clase proxy tampoco está especificado. Tenga en cuenta que packagesealing no impedirá que una clase proxy se defina con éxito en un paquete en particular en tiempo de ejecución, y tampoco las clases ya definidas en el mismo cargador de clases y el mismo paquete con firmantes particulares.
- Dado que una clase proxy implementa todas las interfaces especificadas en su creación, invocar
getInterfaces
en su objetoClass
devolverá un array que contiene la misma lista de interfaces (en el orden especificado en su creación), invocargetMethods
en su objetoClass
devolverá un array de objetosMethod
que incluyen todos los métodos en esas interfaces, e invocargetMethod
encontrará métodos en las interfaces proxy como se esperaría. - El método
Proxy.isProxyClass
devolverá true si se pasa una clase proxy a una clase devuelta porProxy.getProxyClass
o la clase de un objeto devuelto porProxy.newProxyInstance
and y false de lo contrario. La confiabilidad de este método es importante para la capacidad de usar o para tomar decisiones de seguridad, por lo que su implementación no solo debe probar si la clase en cuestión se extiendejava.lang.reflect.Proxy
. - El
java.security.ProtectionDomain
de un proxyclass es el mismo que el de las clases de sistema cargadas por el cargador de clase de arranque, comojava.lang.Object
, porque el código de una clase proxy es generado por código de sistema de confianza. Este dominio de protección normalmente se otorgarájava.security.AllPermission
.
Crear una instancia de proxy
Cada clase de proxy tiene un constructor público que toma oneargument, una implementación de la interfaz InvocationHandler
.
Cada instancia de proxy tiene un objeto controlador de invocaciones asociado, el que se pasó a su constructor. En lugar de tener que utilizar la API de reflexión para acceder al constructor público, también se puede crear una instancia proxy llamando al métodoProxy.newProxyInstance
, que combina las acciones de llamar a Proxy.getProxyClass
con invocar al constructor con un controlador de invocación.Proxy.newProxyInstance
lanzaIllegalArgumentException
por las mismas razones queProxy.getProxyClass
.
Propiedades de instancia de proxy
Una instancia de proxy tiene las siguientes propiedades:
- Dada una instancia proxy
proxy
y una de las interfaces implementadas por su clase proxyFoo
, la siguiente expresión devolverá true:proxy instanceof Foo
y la siguiente operación de conversión tendrá éxito (en lugar de lanzar una
ClassCastException
):(Foo) proxy
- El método estático
Proxy.getInvocationHandler
devolverá como argumento el controlador de invocaciones asociado con el proxy instancepassed. Si el objeto pasado aProxy.getInvocationHandler
no es una instancia proxy,se lanzará unIllegalArgumentException
. - Una invocación de método de interfaz en una instancia proxy se codificará y enviará al método
invoke
del controlador de invocaciones, como se describe a continuación.La instancia proxy en sí se pasará como el primer argumento de
invoke
, que es de tipoObject
.El segundo argumento pasado a
invoke
será la instanciajava.lang.reflect.Method
correspondiente al método de interfaz invocado en la instancia proxy. La clase declarante del objetoMethod
será la interfaz en la que se declaró el método, que puede ser una superinterface de la interfaz proxy a través de la cual la clase proxy hereda el método.El tercer argumento pasado a
invoke
será una serie de objetos que contienen los valores de los argumentos pasados en la invocación del método en la instancia proxy. Los argumentos de primitivetypes se envuelven en una instancia de la clase primitivewrapper apropiada, comojava.lang.Integer
ojava.lang.Boolean
. La implementación del métodoinvoke
es libre de modificar el contenido de thisarray.El valor devuelto por el método
invoke
se convertirá en el valor devuelto de la invocación del método en la instancia proxy. Si el valor de retorno declarado del método de interfaz es un tipo primitivo, entonces el valor devuelto porinvoke
debe ser una instancia de la clase de envoltura primitiva correspondiente; de lo contrario,debe ser un tipo asignable al tipo de retorno declarado. Si el valor devuelto porinvoke
esnull
y el tipo de retorno del método de interfaz es primitivo, la ubicación del método lanzará unNullPointerException
en la instancia proxy. Si el valor devuelto porinvoke
no es compatible con el tipo de retorno declarado del método como se describe anteriormente, la instancia proxy lanzará unClassCastException
.Si una excepción es lanzada por el método
invoke
, también será lanzada por la invocación del método en la instancia proxy.El tipo de excepción debe asignarse a cualquiera de los tipos de excepción declarados en la firma del método de interfaz o a los tipos de excepción no marcadosjava.lang.RuntimeException
ojava.lang.Error
. Siinvoke
lanza una excepción marcada que no es asignable a ninguno de los tipos de excepción declarados en la cláusulathrows
del método de interfaz, la invocación del método lanzará unUndeclaredThrowableException
en la instancia proxy. ElUndeclaredThrowableException
se construirá con la excepción que fue lanzada por el métodoinvoke
. - Una invocación de los métodos
hashCode
,equals
otoString
declarados enjava.lang.Object
en una instancia proxy se codificará y enviará al métodoinvoke
del controlador de invocaciones de la misma manera que se codifican y envían las invocaciones de métodos de interfaz, como se describió anteriormente. La clase declarante del objetoMethod
pasado ainvoke
serájava.lang.Object
. Otros métodos públicos de una instancia proxy heredada dejava.lang.Object
no son reemplazados por una clase proxy, por lo que las invocaciones de esos métodos se comportan como lo hacen para instancias dejava.lang.Object
.
Métodos Duplicados en Interfaces MultipleProxy
Cuando dos o más interfaces de una clase proxy contienen un método con el mismo nombre y firma de parámetro, el orden de las interfaces del proxyclass se vuelve significativo. Cuando se invoca dicho método de duplicación en una instancia de proxy, el objeto Method
pasado al controlador de invocación no será necesariamente el que tenga la clase declarante asignable desde el tipo de referencia de la interfaz a través de la cual se invocó el método del proxy. Esta limitación existe porque la implementación del método correspondiente en la clase proxy generada no puede determinar a través de qué interfaz se invocó. Por lo tanto, cuando se invoca un método duplicado en una instancia proxy, el objeto Method
para el método en la interfaz principal que contiene el método (ya sea directamente o heredado a través de una superinterface) en la lista de interfaces de la clase proxy se pasa al métodoinvoke
del controlador de invocación, independientemente del tipo de referencia a través del cual se produjo la invocación del método.
Si una interfaz proxy contiene un método con el mismo nombre y firma de parámetros que los métodos hashCode
,equals
o toString
dejava.lang.Object
, cuando se invoca dicho método en una instancia de roxy, el objeto Method
pasado al controlador de localización tendrá java.lang.Object
como clase de declaración. En otras palabras, los métodos públicos no finales dejava.lang.Object
preceden lógicamente a todas las interfaces proxy para la determinación de qué objeto Method
pasar al controlador de invocación.
Tenga en cuenta también que cuando se envía un método duplicado a un manejador de invocación, el método invoke
solo puede lanzar tipos de excepción comprobados que se pueden asignar a uno de los tipos de excepción en la cláusula throws
del método en todas las interfaces proxy a través de las que se puede invocar. Si el métodoinvoke
lanza una excepción marcada que no se puede asignar a ninguno de los tipos de excepción declarados por el método en una de las interfaces proxy a través de las cuales se puede invocar, entonces una excepción marcada UndeclaredThrowableException
será lanzada por la invocación en la instancia proxy. Esta restricción significa que no todos los tipos de excepción devueltos invocandogetExceptionTypes
en el Method
objectpassed al método invoke
pueden lanzarse necesariamente con éxito por el método invoke
.
Serialización
Dado que java.lang.reflect.Proxy
implementajava.io.Serializable
, las instancias de proxy pueden ser serializadas, como se describe en esta sección. Sin embargo, si una instancia proxy contiene un manejador de invocaciones que no se puede asignar ajava.io.Serializable
, se lanzará unjava.io.NotSerializableException
si se escribe una instancia ajava.io.ObjectOutputStream
. Tenga en cuenta que para proxyclasses, la implementación de java.io.Externalizable
tiene el mismo efecto con respecto a la serialización que la implementación dejava.io.Serializable
: los métodos writeExternal
y readExternal
de la interfazExternalizable
nunca se invocarán en una instancia de roxy (o un controlador de invocaciones) como parte de su proceso de serialización. Al igual que con todos los objetos Class
, el objetoClass
para una clase proxy siempre es serializable.
Una clase proxy no tiene campos serializables yserialVersionUID
de 0L
. En otras palabras, cuando el objeto Class
para una clase proxy se pasa al método estático lookup
dejava.io.ObjectStreamClass
, la instanciaObjectStreamClass
devuelta tendrá las siguientes propiedades:
- Invocar su método
getSerialVersionUID
devolverá0L
. - Invocar su método
getFields
devolverá un arrayof de longitud cero. - Invocar su método
getField
con cualquier argumentoString
devolveránull
.
El protocolo stream para Serialización de objetos admite un código de tipo llamado TC_PROXYCLASSDESC
, que es un símbolo de terminación en la gramática para el formato stream; su tipo y valor están definidos por el siguiente campo constante en la interfazjava.io.ObjectStreamConstants
:
final static byte TC_PROXYCLASSDESC = (byte)0x7D;
La gramática también incluye las siguientes dos reglas, la primera de las cuales es una expansión alternativa de la regla newClassDescrule original:
Newclassdescinfo:TC_PROXYCLASSDESC
Newhandle proxyClassDescInfo
proxyClassDescInfo:(int)<count>
proxyInterfaceName classAnnotationsuperClassDesc
Nombre de interfaz proxy:(utf)
Cuando un ObjectOutputStream
serializa el classdescriptor para una clase que es una clase proxy, como se determina sin pasar por su objeto Class
al métodoProxy.isProxyClass
, utiliza el código de tipoTC_PROXYCLASSDESC
en lugar deTC_CLASSDESC
, siguiendo las reglas anteriores. En la expansión de proxyClassDescInfo, la secuencia de elementos deproxyInterfaceName son los nombres de todas las interfaces implementadas por la clase proxy, en el orden en que se devuelven invocando el método getInterfaces
en el objeto Class
. Los elementos classAnnotation Ysuperclassdesc tienen el mismo significado que en la regla Classdescinfo. Para una clase proxy, Superclassdesci es el descriptor de clase para su superclase,java.lang.reflect.Proxy
; incluyendo este descriptor, permite la evolución de la representación serializada de la clase Proxy
para instancias proxy.
Para clases no proxy, ObjectOutputStream
llama al método itsprotected annotateClass
para permitir que las subclases escriban datos personalizados en la secuencia de una clase en particular. Para proxyclasses, en lugar de annotateClass
, el método siguiente en java.io.ObjectOutputStream
se llama con el objeto Class
para la clase proxy:
protected void annotateProxyClass(Class cl) throws IOException;
La implementación predeterminada de annotateProxyClass
enObjectOutputStream
no hace nada.
Cuando un ObjectInputStream
encuentra el código de tipoTC_PROXYCLASSDESC
, deserializa el classdescriptor para una clase proxy de la secuencia, con el formato descrito anteriormente. En lugar de llamar a su método resolveClass
para resolver el objeto Class
para el classdescriptor, se llama al siguiente método enjava.io.ObjectInputStream
:
protected Class resolveProxyClass(String interfaces) throws IOException, ClassNotFoundException;
La lista de nombres de interfaz que se deserializaron en el descriptor proxyclass se pasa como el interfaces
argumentto resolveProxyClass
.
La implementación predeterminada de resolveProxyClass
enObjectInputStream
devuelve los resultados de llamar aProxy.getProxyClass
con la lista de objetosClass
para las interfaces nombradas en el parámetrointerfaces
. El objeto Class
usado para cada nombre de interfaz i
es el valor reajustado llamando a
Class.forName(i, false, loader)
dondeloader
es el primer cargador de clase no nula en la pila de ejecución, onull
si no hay cargadores de clase no nula en la pila. Esta es la misma elección de cargador de clases realizada por el comportamiento predeterminado del métodoresolveClass
. Este samevalue deloader
es también el cargador de clases pasado aProxy.getProxyClass
. SiProxy.getProxyClass
lanza unIllegalArgumentException
,resolveClass
lanzará unClassNotFoundException
que contiene elIllegalArgumentException
.
Dado que una clase proxy nunca tiene sus propios campos serializables, los datos de clase en la representación de flujo de una instancia proxy constan en su totalidad de los datos de instancia de su superclase,java.lang.reflect.Proxy
. Proxy
tiene un campo onerializable, h
, que contiene el controlador de invocación para la instancia proxy.
Ejemplos
Aquí hay un ejemplo simple que imprime un mensaje antes y después de la invocación de un método en un objeto que implementa una lista arbitraria de interfaces:
public interface Foo { Object bar(Object obj) throws BazException;}public class FooImpl implements Foo { Object bar(Object obj) throws BazException { // ... }}public class DebugProxy implements java.lang.reflect.InvocationHandler { private Object obj; public static Object newInstance(Object obj) { return java.lang.reflect.Proxy.newProxyInstance( obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new DebugProxy(obj)); } private DebugProxy(Object obj) { this.obj = obj; } public Object invoke(Object proxy, Method m, Object args) throws Throwable { Object result; try { System.out.println("before method " + m.getName()); result = m.invoke(obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } catch (Exception e) { throw new RuntimeException("unexpected invocation exception: " + e.getMessage()); } finally { System.out.println("after method " + m.getName()); } return result; }}
Para construir un DebugProxy
para una implementación de la interfaz Foo
y llamar a uno de sus métodos:
Foo foo = (Foo) DebugProxy.newInstance(new FooImpl()); foo.bar(null);
Este es un ejemplo de una clase de manejador de invocaciones de utilidad que proporciona un comportamiento de proxy predeterminado para métodos heredados dejava.lang.Object
e implementa la delegación de ciertas invocaciones de métodos proxy a objetos distintos en función de la interfaz del método invocado:
import java.lang.reflect.*;public class Delegator implements InvocationHandler { // preloaded Method objects for the methods in java.lang.Object private static Method hashCodeMethod; private static Method equalsMethod; private static Method toStringMethod; static { try { hashCodeMethod = Object.class.getMethod("hashCode", null); equalsMethod = Object.class.getMethod("equals", new Class { Object.class }); toStringMethod = Object.class.getMethod("toString", null); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } private Class interfaces; private Object delegates; public Delegator(Class interfaces, Object delegates) { this.interfaces = (Class) interfaces.clone(); this.delegates = (Object) delegates.clone(); } public Object invoke(Object proxy, Method m, Object args) throws Throwable { Class declaringClass = m.getDeclaringClass(); if (declaringClass == Object.class) { if (m.equals(hashCodeMethod)) { return proxyHashCode(proxy); } else if (m.equals(equalsMethod)) { return proxyEquals(proxy, args); } else if (m.equals(toStringMethod)) { return proxyToString(proxy); } else { throw new InternalError( "unexpected Object method dispatched: " + m); } } else { for (int i = 0; i < interfaces.length; i++) { if (declaringClass.isAssignableFrom(interfaces)) { try { return m.invoke(delegates, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } } return invokeNotDelegated(proxy, m, args); } } protected Object invokeNotDelegated(Object proxy, Method m, Object args) throws Throwable { throw new InternalError("unexpected method dispatched: " + m); } protected Integer proxyHashCode(Object proxy) { return new Integer(System.identityHashCode(proxy)); } protected Boolean proxyEquals(Object proxy, Object other) { return (proxy == other ? Boolean.TRUE : Boolean.FALSE); } protected String proxyToString(Object proxy) { return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode()); }}
Las subclases de Delegator
pueden anularinvokeNotDelegated
para implementar el comportamiento de las invocaciones de proxymethod que no se deleguen directamente a otros objetos,y pueden anular proxyHashCode
,proxyEquals
y proxyToString
para modificar el comportamiento predeterminado de los métodos heredados por el proxy de java.lang.Object
.
Para construir un Delegator
para una implementación de la interfaz Foo
:
Class proxyInterfaces = new Class { Foo.class }; Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), proxyInterfaces, new Delegator(proxyInterfaces, new Object { new FooImpl() }));
Tenga en cuenta que la implementación de Delegator
classgiven anterior pretende ser más ilustrativa que optimizada; por ejemplo, en lugar de almacenar en caché y comparar los objetos Method
para los métodos hashCode
, equals
ytoString
, simplemente podría compararlos por sus nombres de cadena, porque ninguno de esos nombres de método está sobrecargado enjava.lang.Object
.