Clases de proxy dinámico

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 Objectque 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 objetos Class 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 clasescl y cada interfaz i, 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), invocar getMethods en su objeto Class devolverá un array de objetos Method 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 por Proxy.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, como java.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 proxy Foo, 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á un IllegalArgumentException.
  • Una invocación de método de interfaz en una instancia proxy se codificará y enviará al métodoinvoke 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 tipo Object.

    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 objeto Method 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, como java.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 por invoke 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 por invoke es null 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áusula throws del método de interfaz, la invocación del método lanzará un UndeclaredThrowableExceptionen la instancia proxy. ElUndeclaredThrowableException se construirá con la excepción que fue lanzada por el método invoke.

  • Una invocación de los métodos hashCode,equals o toString declarados enjava.lang.Object en una instancia proxy se codificará y enviará al método invokedel 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 objeto Method pasado a invoke será java.lang.Object. Otros métodos públicos de una instancia proxy heredada de java.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 de java.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 Methodpasado 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 Methodpasar 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 writeExternaly 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_PROXYCLASSDESCNewhandle 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)

dondeloaderes el primer cargador de clase no nula en la pila de ejecución, onullsi 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 deloaderes también el cargador de clases pasado aProxy.getProxyClass. SiProxy.getProxyClasslanza unIllegalArgumentException,resolveClasslanzará unClassNotFoundExceptionque 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 anularinvokeNotDelegatedpara implementar el comportamiento de las invocaciones de proxymethod que no se deleguen directamente a otros objetos,y pueden anular proxyHashCode,proxyEquals y proxyToStringpara 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 Methodpara 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.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.