Como sabemos, Spring usa proxies para agregar funcionalidad ( @Transactional
y @Scheduled
por ejemplo). Hay dos opciones: usar un proxy dinámico JDK (la clase tiene que implementar interfaces no vacías) o generar una clase secundaria usando el generador de código CGLIB. Siempre pensé que proxyMode me permite elegir entre un proxy dinámico JDK y CGLIB.
Pero pude crear un ejemplo que muestra que mi suposición es incorrecta:
Caso 1:
Único:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
public void foo() {
System.out.println(myBeanB.getCounter());
}
public MyBeanB getMyBeanB() {
return myBeanB;
}
}
Prototipo:
@Service
@Scope(value = "prototype")
public class MyBeanB {
private static final AtomicLong COUNTER = new AtomicLong(0);
private Long index;
public MyBeanB() {
index = COUNTER.getAndIncrement();
System.out.println("constructor invocation:" + index);
}
@Transactional // just to force Spring to create a proxy
public long getCounter() {
return index;
}
}
Principal:
MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());
Salida:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
Aquí podemos ver dos cosas:
MyBeanB
fue instanciado solo una vez .- Para agregar la
@Transactional
funcionalidad paraMyBeanB
, Spring usó CGLIB.
Caso 2:
Déjame corregir la MyBeanB
definición:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
En este caso, la salida es:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
Aquí podemos ver dos cosas:
MyBeanB
fue instanciado 3 veces.- Para agregar la
@Transactional
funcionalidad paraMyBeanB
, Spring usó CGLIB.
¿Podría explicar lo que está pasando? ¿Cómo funciona realmente el modo proxy?
PD
He leído la documentación:
/**
* Specifies whether a component should be configured as a scoped proxy
* and if so, whether the proxy should be interface-based or subclass-based.
* <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
* that no scoped proxy should be created unless a different default
* has been configured at the component-scan instruction level.
* <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
* @see ScopedProxyMode
*/
Pero no está claro para mí.
Actualizar
Caso 3:
Investigué un caso más, en el que extraje la interfaz de MyBeanB
:
public interface MyBeanBInterface {
long getCounter();
}
@Service
public class MyBeanA {
@Autowired
private MyBeanBInterface myBeanB;
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanB implements MyBeanBInterface {
y en este caso el resultado es:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
Aquí podemos ver dos cosas:
MyBeanB
fue instanciado 3 veces.- Para agregar la
@Transactional
funcionalidadMyBeanB
, Spring usó un proxy dinámico JDK.
MyBeanB
clase no extiende ninguna interfaz, por lo que no es sorprendente que el registro de la consola muestre instancias de proxy CGLIB. En el caso 3, introduce e implementa una interfaz, por lo tanto, obtiene un proxy JDK. Incluso lo describe en su texto introductorio.<aop:config proxy-target-class="true">
o@EnableAspectJAutoProxy(proxyTargetClass = true)
, respectivamente.Respuestas:
El proxy generado para el
@Transactional
comportamiento tiene un propósito diferente que los proxies con ámbito.El
@Transactional
proxy es uno que envuelve el bean específico para agregar un comportamiento de administración de sesión. Todas las invocaciones de métodos realizarán la gestión de transacciones antes y después de delegar en el bean real.Si lo ilustras, se vería como
Para nuestros propósitos, esencialmente puede ignorar su comportamiento (eliminar
@Transactional
y debería ver el mismo comportamiento, excepto que no tendrá el proxy cglib).El
@Scope
proxy se comporta de manera diferente. La documentación dice:Lo que Spring realmente está haciendo es crear una definición de bean singleton para un tipo de fábrica que representa el proxy. Sin embargo, el objeto proxy correspondiente consulta el contexto del bean real para cada invocación.
Si lo ilustras, se vería como
Como
MyBeanB
es un bean prototipo, el contexto siempre devolverá una nueva instancia.A los fines de esta respuesta, suponga que recuperó el
MyBeanB
directamente conque es esencialmente lo que hace Spring para satisfacer un
@Autowired
objetivo de inyección.En tu primer ejemplo,
Usted declara una definición de bean prototipo (a través de las anotaciones).
@Scope
tiene unproxyMode
elemento queEntonces Spring no está creando un proxy con alcance para el bean resultante. Recuperas ese frijol con
Ahora tiene una referencia a un nuevo
MyBeanB
objeto creado por Spring. Esto es como cualquier otro objeto Java, las invocaciones de métodos irán directamente a la instancia referenciada.Si lo usó
getBean(MyBeanB.class)
nuevamente, Spring devolvería una nueva instancia, ya que la definición de bean es para un bean prototipo . No está haciendo eso, por lo que todas las invocaciones de métodos van al mismo objeto.En tu segundo ejemplo,
declara un proxy con ámbito que se implementa a través de cglib. Al solicitar un bean de este tipo a Spring con
Spring sabe que
MyBeanB
es un proxy con alcance y, por lo tanto, devuelve un objeto proxy que satisface la API deMyBeanB
(es decir, implementa todos sus métodos públicos) que internamente sabe cómo recuperar un bean de tipo realMyBeanB
para cada invocación de método.Intenta correr
Esto devolverá una
true
pista al hecho de que Spring está devolviendo un objeto proxy singleton (no un bean prototipo).En una invocación de método, dentro de la implementación de proxy, Spring usará una
getBean
versión especial que sepa cómo distinguir entre la definición de proxy y laMyBeanB
definición de bean real . Eso devolverá una nuevaMyBeanB
instancia (ya que es un prototipo) y Spring le delegará la invocación del método a través de la reflexión (clásicoMethod.invoke
).Su tercer ejemplo es esencialmente el mismo que su segundo.
fuente
context.getBean(MyBeanB.class)
, en realidad no estás obteniendo el proxy, estás obteniendo el bean real.@Autowired
obtiene el proxy (de hecho, fallará si inyecta enMyBeanB
lugar del tipo de interfaz). No sé por qué Spring te permite hacergetBean(MyBeanB.class)
con INTERFACES.@Transactional
. Con@Autowired MyBeanBInterface
proxies y con ámbito, Spring inyectará el objeto proxy.getBean(MyBeanB.class)
Sin embargo, si solo lo hace , Spring no devolverá el proxy, devolverá el bean de destino.