El contenedor IoC de Spring gestiona no solo la creación de instancias de sus objetos (beans), sino también la vinculación de objetos que interactúan (o dependencias). Si necesita inyectar (por ejemplo) un bean que está dentro del alcance de una solicitud HTTP en otro bean con un alcance a largo plazo, puede inyectar un proxy AOP en lugar del bean que está dentro del alcance. Es decir, necesita implementar un objeto proxy que exponga la misma interfaz pública que el objeto dentro del alcance, pero que también pueda recuperar el objeto de destino real del alcance correspondiente (por ejemplo, solicitud HTTP) y delegar llamadas a métodos al objeto real. .

También puede usar <aop:scoped-proxy/> entre beans definidos como singleton, con la referencia pasando a través de un proxy intermedio que es serializable. y, por lo tanto, puede volver a obtener el bean singleton objetivo tras la deserialización.

Cuando declaras <aop:scoped-proxy/> para un bean de alcance de visibilidad prototype, cada llamada a un método en el proxy compartido da como resultado la creación de un nueva instancia de destino, a la que luego se transfiere la llamada.

Además, los proxies con ámbito no son la única forma de acceder a beans desde ámbitos más cortos de una manera segura para el ciclo de vida. También es posible declarar el punto de inyección (es decir, un argumento constructor o definidor o un campo enlazado automáticamente) como ObjectFactory<MyTargetBean>, lo que permite la llamada a getObject(). para recuperar la instancia actual a pedido cada vez que sea necesario, sin tener que conservar la instancia ni almacenarla por separado.

Como opción extendida, puede declarar un ObjectProvider<MyTargetBean>, que proporciona varias opciones de acceso adicionales, incluidas getIfAvailable y getIfUnique.

La opción JSR-330 se llama Provider y se usa con una declaración Provider<MyTargetBean> y una llamada get() correspondiente para cada intento de obtención.

La configuración en el siguiente ejemplo es solo una línea, pero es importante entender por qué y cómo funciona:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/> 
    </bean>
    <!-- a bean in the scope of a singleton, injected by proxy to the above bean -->
    <bean id="userService" class="com.something.SimpleUserService">
        <!-- link to the proxied bean userPreferences -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>
  1. String defining the proxy

Para crear un proxy de este tipo, debe insertar un elemento secundario <aop:scoped-proxy/> en la definición del bean dentro del alcance. ¿Por qué las definiciones de beans que están dentro del alcance de request, session o niveles de alcance especiales requieren un <aop:scoped-proxy/> de elemento? Considere la siguiente definición de un bean singleton y compárela con lo que necesita definir para los ámbitos anteriores (tenga en cuenta que la siguiente definición del bean userPreferences tal como está está incompleta):

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

En el ejemplo anterior, el bean único (userManager) se implementa utilizando una referencia al bean (userPreferences), ubicado en el área de visibilidad de HTTP. session. El punto importante aquí es que el bean userManager es un objeto singleton: se crea una instancia exactamente una vez para cada contenedor y sus dependencias (en este caso, solo una, el bean userPreferences ) ) también se implementan solo una vez. Esto significa que el bean userManager solo funciona con el mismo objeto userPreferences (es decir, aquel con el que se implementó originalmente).

Este no es el comportamiento apropiado al inyectar un bean con un ciclo de vida más corto que está dentro del alcance en un bean con un ciclo de vida más largo que está dentro del alcance (por ejemplo, inyectar un bean interoperativo que está dentro del alcance del código HTTP session, como una dependencia en un único bean). Más bien, requiere un objeto userManager y, durante la duración de la sesión HTTP, un objeto userPreferences específico para esa session . Entonces, el contenedor crea un objeto que expone exactamente la misma interfaz pública que la clase UserPreferences (idealmente un objeto que es una instancia de UserPreferences) que puede recibir el realUserPreferences object UserPreferences del mecanismo de definición (solicitud HTTP, session, etc.). El contenedor inyecta este objeto proxy en el bean userManager, que no sabe que esta referencia UserPreferences es un proxy. En este ejemplo, cuando una instancia de UserManager inicia un método en un objeto UserPreferences con una dependencia inyectada, en realidad llama a un método en el proxy. Luego, el proxy recupera el objeto UserPreferences real de (en este caso) la Session HTTP y delega la llamada al método al objeto UserPreferences real resultante.

Por lo tanto, se requiere la siguiente configuración (correcta y completa) al inyectar beans dentro del alcance de request y session en objetos colaboradores, como se muestra en el siguiente ejemplo:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

Seleccionar el tipo de proxy a crear

De forma predeterminada, cuando un contenedor Spring crea un proxy para un bean marcado con el elemento <aop:scoped-proxy/>, se crea un proxy de clase basado en CGLIB.

¡Los proxies CGLIB sólo interceptan llamadas a métodos públicos! No llame a métodos no públicos en dicho proxy. No se delegan al objeto de destino real que está dentro del alcance.

Como alternativa, puede configurar el contenedor Spring para crear servidores proxy estándar basados en JDK para dichos beans dentro del alcance especificando false para el valor del atributo proxy-target-class. elemento <aop:scoped-proxy/>. El uso de un proxy basado en la interfaz JDK significa que no necesitará bibliotecas adicionales en la ruta de clase de su aplicación para afectar dicho proxy. Sin embargo, esto también significa que la clase de un bean con ámbito debe implementar al menos una interfaz, y que todos los objetos de comunicación en los que está incrustado el bean con ámbito deben hacer referencia a él a través de una de sus interfaces. El siguiente ejemplo muestra un proxy basado en interfaz:

<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>