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>
- 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>