Si utiliza un contenedor Spring IoC (ApplicationContext o BeanFactory) para sus objetos comerciales (¡y debería hacerlo!), su tarea es utilizar una de las implementaciones FactoryBean de Spring AOP. (Recuerde que el bean de fábrica introduce una capa de indirección, que le permite crear otros tipos de objetos).

Los ayudantes de Spring AOP también usan fábrica oculta beans.

La forma principal de crear un proxy AOP en Spring es usar org.springframework.aop.framework.ProxyFactoryBean. Le brinda control total sobre los cortes, los consejos que se aplican y su orden. Sin embargo, existen opciones más simples que son preferibles si no necesita dicho control.

Conceptos básicos

ProxyFactoryBean, al igual que otras implementaciones de FactoryBean de Spring, introduce un nivel de direccionamiento indirecto. Si define un ProxyFactoryBean con el nombre foo, los objetos que hacen referencia a foo no ven la instancia de ProxyFactoryBean en sí, pero el objeto, creado por la implementación del método getObject() en ProxyFactoryBean. Este método crea un proxy AOP que envuelve el objeto de destino.

Una de las ventajas más importantes de usar un ProxyFactoryBean u otra clase compatible con IoC para crear un proxy AOP es que y los cortes también se pueden controlar a través de IoC. Esta es una característica eficaz que le permite adoptar ciertos enfoques que son difíciles de implementar utilizando otros marcos de AOP. Por ejemplo, la propia placa puede hacer referencia a objetos de aplicación (además del objetivo, que debe estar en cualquier marco AOP), aprovechando al máximo la modularidad que aporta la inyección de dependencia.

Propiedades de JavaBean

Como y para la mayoría de las implementaciones FactoryBean proporcionadas por Spring, la clase ProxyFactoryBean es en sí misma una clase JavaBean. Sus propiedades se utilizan para:

  • Especificar el destino que necesita ser proxy.

  • Especificar si se utiliza CGLIB (descrito a continuación , consulte también la sección "Proxies basados en JDK y CGLIB").

Algunas propiedades clave se heredan de org.springframework.aop.framework.ProxyConfig (superclase para todas las fábricas de proxy AOP en Spring). Estas propiedades clave constan de:

  • proxyTargetClass: es true si la clase de destino será proxy en lugar de las interfaces de la clase de destino. . Si esta propiedad se establece en true, se crean proxies CGLIB.

  • optimize: controla si se utilizan métodos de optimización agresivos para servidores proxy creados con CGLIB. Esta opción no debe usarse a la ligera a menos que comprenda cómo el proxy AOP correspondiente controla la optimización. Actualmente sólo se utiliza para el proxy CGLIB. El parámetro no afecta a los proxies JDK dinámicos.

  • frozen: si la configuración del proxy tiene la propiedad frozen, entonces cambia ya no está permitido. Esto se puede utilizar tanto para una optimización ligera como en los casos en los que no desea que las personas que llaman puedan manipular el proxy (a través de la interfaz Advised) después de que se haya creado. El valor predeterminado de esta propiedad es false, por lo que se permiten cambios (como agregar sugerencias adicionales).

  • exposeProxy : Define si el proxy actual debe abrirse en ThreadLocal para que el destino pueda acceder a él. Si el objetivo necesita obtener un proxy y la propiedad exposeProxy está establecida en true, el objetivo puede usar el método AopContext.currentProxy().

Otras propiedades específicas de ProxyFactoryBean son:

  • proxyInterfaces: Nombres de interfaz de matriz String. Si no se especifica esta propiedad, entonces se utiliza el proxy CGLIB para la clase de destino.

  • interceptorNames: matriz String de nombres Advisor, interceptores u otros consejos a aplicar. El orden juega un papel importante, todo sucede en orden de prioridad. Por lo tanto, el primer interceptor de la lista será el primero en interceptar la llamada.

    Los nombres son los nombres de los beans en la fábrica actual, incluidos los nombres de los beans de las fábricas principales. En este caso, no puede mencionar referencias de beans, ya que esto hará que ProxyFactoryBean ignore el parámetro del objeto singleton en la sugerencia.

    Puede agregar el nombre del interceptor con un asterisco (l*). Esto hará que se apliquen todos los beans asesores con nombres que comiencen con la parte anterior al asterisco.

  • singleton: si la fábrica debe devolver un único objeto, sin importar la frecuencia con la que Se llama al método getObject(). Varias implementaciones de FactoryBean proporcionan dicho método. El valor predeterminado es true. Si necesita utilizar una sugerencia con estado (por ejemplo, para mixins con estado), use sugerencias de prototipo junto con un valor de propiedad singleton de false.

Proxies basados en JDK y CGLIB

Esta sección sirve como documentación prescriptiva sobre cómo ProxyFactoryBean elige entre crear un proxy basado en JDK o CGLIB para un objeto de destino en particular (que debe ser proxy). ).

La lógica detrás de ProxyFactoryBean con respecto a la creación de un proxy basado en JDK o CGLIB ha cambiado entre las versiones 1.2.x y 2.0. Primavera. ProxyFactoryBean ahora tiene la misma semántica con respecto a la detección automática de interfaces que la clase TransactionProxyFactoryBean.

Si la clase del objeto de destino que se va a utilizar como proxy ( (en adelante simplemente llamada clase de destino), no implementa ninguna interfaz, se crea un proxy basado en CGLIB. Este es el escenario más simple porque los servidores proxy JDK se basan en interfaces y la falta de interfaces significa que el proxy JDK no es posible en absoluto. Puede conectar el bean de destino y especificar una lista de interceptores configurando la propiedad interceptorNames. Tenga en cuenta que se crea un proxy basado en CGLIB incluso si la propiedad proxyTargetClass en ProxyFactoryBean se estableció en false. (Hacer esto no tiene sentido y es mejor eliminar esta propiedad de la definición del bean porque es redundante en el mejor de los casos y confusa en el peor.)

Si la clase objetivo implementa una (o más) interfaces, entonces la El tipo de proxy creado depende de la configuración de ProxyFactoryBean.

Si la propiedad proxyTargetClass en ProxyFactoryBean se estableció en true, entonces se crea un proxy basado en CGLIB. Esto es lógico y coherente con el principio de mínima sorpresa. Incluso si la propiedad proxyInterfaces en ProxyFactoryBean se estableció en uno o más nombres de interfaz completos, el hecho de que la propiedad proxyTargetClass esté establecida en true hace que se produzca proxy basado en CGLIB.

Si la propiedad proxyInterfaces en ProxyFactoryBean se estableció en uno o más completamente nombres de interfaz calificados, se crea un proxy basado en el JDK. El proxy creado implementa todas las interfaces que se especificaron en la propiedad proxyInterfaces. Si la clase de destino implementa muchas más interfaces que las especificadas en la propiedad proxyInterfaces, eso es genial, pero esas interfaces adicionales no serán implementadas por el proxy devuelto.

Si el proxyInterfaces en ProxyFactoryBean no se especificó, pero la clase de destino implementa una (o más) interfaces, ProxyFactoryBean detecta automáticamente el hecho de que la clase de destino se implementa en al menos una interfaz y se crea un proxy basado en el JDK. Las interfaces que en realidad son proxy son todas las interfaces que implementa la clase de destino. Esto es esencialmente lo mismo que especificar una lista de todas y cada una de las interfaces que la clase de destino implementa para la propiedad proxyInterfaces. Sin embargo, requiere mucho menos trabajo y es menos probable que se cometan errores tipográficos.

Interfaces de proxy

Veamos un ejemplo simple de ProxyFactoryBean en acción. Este ejemplo incluye:

  • El bean de destino que será proxy. Esta es la definición del bean personTarget en el ejemplo.

  • Advisor y Interceptor se utiliza para proporcionar asesoramiento.

  • Defina un bean proxy AOP para especificar el objeto de destino (el bean personTarget), las interfaces para el proxy y el consejos que se aplicarán.

El siguiente listado muestra un ejemplo:


   <bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name" value="Tony"/>
    <property name="age" value="51"/>
</bean>
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>
<bean id="person"
      class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>
    <property name="target" ref="personTarget"/>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

Tenga en cuenta que la propiedad interceptorNames acepta una lista String que contiene nombres de beans interceptores o asesores en la fábrica actual. Puede utilizar asesores, interceptores, objetos con consejos "antes", "después del retorno" y "lanzar excepción". El orden de los asesores es importante.

Quizás te preguntes por qué no hay enlaces a beans en la lista. La razón es que si la propiedad del objeto singleton en ProxyFactoryBean se establece en false, debe poder devolver instancias de proxy independientes. Si alguno de los asesores es un prototipo, entonces será necesario devolver una instancia independiente, por lo que es necesario poder obtener una instancia del prototipo de fábrica. Una sola referencia no es suficiente.

La definición del bean person mostrada anteriormente se puede utilizar en lugar de la implementación Person de la siguiente manera:

Java
Persona persona = (Persona) factory.getBean("persona");
Kotlin
val person = factory.getBean("person") as Person;

Otros beans en el mismo contexto de IoC puede expresar fuertemente una dependencia escrita de él, al igual que un objeto Java normal. El siguiente ejemplo muestra cómo hacer esto:

 
<bean id="personUser" class="com.mycompany.PersonUser">
    <property name="person"><ref bean="person"/></property>
</bean>

La clase PersonUser en este ejemplo expone una propiedad de tipo Person . En cuanto al proxy AOP, se puede utilizar de forma transparente en lugar de implementar un tipo de persona "real". Sin embargo, su clase será dinámica. Podrías enviarlo a la interfaz Advised (más sobre esto más adelante).

Puedes ocultar la diferencia entre el objetivo y el proxy usando un bean interno anónimo. Sólo la definición de ProxyFactoryBean es diferente. Los consejos se proporcionan únicamente para que estén completos. El siguiente ejemplo muestra cómo utilizar un bean interno anónimo:


<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>
     <!-- Use an internal bean rather than a local reference to the target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name" value="Tony"/>
            <property name="age" value="51"/>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

La ventaja de utilizar un bean interno anónimo es que solo habrá un objeto Persona. Esto es útil si queremos evitar que los usuarios del contexto de la aplicación obtengan una referencia a un objeto que no cuenta con una pista, o si queremos evitar la ambigüedad en el descubrimiento automático y el enlace durante IoC en Spring. Probablemente también sea una ventaja que la definición ProxyFactoryBean sea autónoma. Sin embargo, hay ocasiones en las que poder recuperar un objetivo de fábrica al que no se le han proporcionado consejos puede ser una ventaja (por ejemplo, en ciertos escenarios de prueba).

Clases de proxy

¿Qué sucede si necesita representar una clase en lugar de una o más interfaces?

Imagine que en nuestro ejemplo anterior no había una interfaz Persona. Necesitábamos brindar asesoramiento a una clase llamada Persona que no implementaba ninguna interfaz comercial. En este caso, puede configurar Spring para usar proxy CGLIB en lugar de proxies dinámicos. Para hacer esto, establezca la propiedad proxyTargetClass del ProxyFactoryBean demostrado anteriormente en true. Si bien es mejor programar en interfaces en lugar de clases, la capacidad de brindar consejos a las clases que no implementan interfaces puede resultar útil cuando se trabaja con código heredado. (En general, Spring no es prescriptivo. Si bien esto hace que sea más fácil seguir las mejores prácticas, puede evitar verse obligado a adoptar un enfoque particular).

Si lo desea, puede forzar el uso de CGLIB en cualquier En este caso, incluso si tiene interfaces.

El proxy CGLIB funciona generando una subclase de la clase objetivo durante la ejecución del programa. Spring configura esta subclase generada para delegar llamadas a métodos al objetivo original. La subclase se utiliza para implementar el patrón Decorador asociándole sugerencias.

El proxy CGLIB generalmente debe ser transparente para los usuarios. Sin embargo, hay algunos puntos que deben tenerse en cuenta:

  • Los métodos

    Final no se pueden recomendar porque no se pueden anular.

  • No es necesario agregar CGLIB a su classpath. A partir de Spring 3.2, CGLIB se volvió a empaquetar y se incluyó en el módulo JAR de Spring-core. En otras palabras, AOP basado en CGLIB funciona de inmediato, al igual que los servidores proxy JDK dinámicos.

La diferencia de rendimiento entre el proxy CGLIB y el proxy dinámico es insignificante. El rendimiento no debería ser un factor decisivo en este caso.

Uso de asesores "globales"

Al agregar un asterisco al nombre del interceptor, todos los asesores con nombres de beans que coincidan con la parte anterior al Se agregan asteriscos a la cadena de asesores. Esto puede resultar útil si necesita agregar un conjunto estándar de asesores "globales". El siguiente ejemplo define dos asesores globales:


<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="service"/>
    <property name="interceptorNames">
        <list>
            <value>global* </value>
        </list>
    </property>
</bean>
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>