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.