Como sabemos, o Spring usa proxies para adicionar funcionalidade ( @Transactional
e @Scheduled
por exemplo). Existem duas opções - usar um proxy dinâmico JDK (a classe precisa implementar interfaces não vazias) ou gerar uma classe filha usando o gerador de código CGLIB. Eu sempre pensei que proxyMode me permite escolher entre um proxy dinâmico JDK e CGLIB.
Mas pude criar um exemplo que mostra que minha suposição está errada:
Caso 1:
Singleton:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
public void foo() {
System.out.println(myBeanB.getCounter());
}
public MyBeanB getMyBeanB() {
return myBeanB;
}
}
Protótipo:
@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;
}
}
A Principal:
MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());
Resultado:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
Aqui podemos ver duas coisas:
MyBeanB
foi instanciado apenas uma vez .- Para adicionar a
@Transactional
funcionalidadeMyBeanB
, o Spring usou o CGLIB.
Caso 2:
Deixe-me corrigir a MyBeanB
definição:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
Nesse caso, a saída é:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
Aqui podemos ver duas coisas:
MyBeanB
foi instanciado 3 vezes.- Para adicionar a
@Transactional
funcionalidadeMyBeanB
, o Spring usou o CGLIB.
Você poderia explicar o que está acontecendo? Como o modo proxy realmente funciona?
PS
Eu li a documentação:
/**
* 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
*/
mas não está claro para mim.
Atualizar
Caso 3:
Investiguei mais um caso, no qual extraí a interface 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 {
e, neste caso, a saída é:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
Aqui podemos ver duas coisas:
MyBeanB
foi instanciado 3 vezes.- Para adicionar a
@Transactional
funcionalidadeMyBeanB
, o Spring usou um proxy dinâmico JDK.
MyBeanB
classe não estende nenhuma interface, portanto, não é de surpreender que o log do console mostre instâncias de proxy CGLIB. No caso 3, você apresenta e implementa uma interface, consequentemente, obtém um proxy JDK. Você até descreve isso em seu texto introdutório.
<aop:config proxy-target-class="true">
ou @EnableAspectJAutoProxy(proxyTargetClass = true)
, respectivamente.