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.Respostas:
O proxy gerado para o
@Transactional
comportamento tem uma finalidade diferente dos proxies com escopo definido.O
@Transactional
proxy é aquele que agrupa o bean específico para adicionar o comportamento de gerenciamento de sessões. Todas as invocações de métodos executarão o gerenciamento de transações antes e depois da delegação ao bean real.Se você ilustrar, pareceria
Para nossos propósitos, você pode essencialmente ignorar seu comportamento (remova
@Transactional
e você deverá ver o mesmo comportamento, exceto que não terá o proxy cglib).O
@Scope
proxy se comporta de maneira diferente. A documentação declara:O que o Spring realmente está fazendo é criar uma definição de bean singleton para um tipo de fábrica que representa o proxy. O objeto proxy correspondente, no entanto, consulta o contexto do bean real para cada chamada.
Se você ilustrar, pareceria
Como
MyBeanB
é um protótipo de bean, o contexto sempre retornará uma nova instância.Para os fins desta resposta, suponha que você recuperou o
MyBeanB
diretamente comque é essencialmente o que o Spring faz para satisfazer um
@Autowired
alvo de injeção.No seu primeiro exemplo,
Você declara uma definição de bean de protótipo (por meio das anotações).
@Scope
tem umproxyMode
elemento quePortanto, o Spring não está criando um proxy com escopo definido para o bean resultante. Você recupera esse bean com
Agora você tem uma referência a um novo
MyBeanB
objeto criado pelo Spring. Assim como qualquer outro objeto Java, as invocações de métodos irão diretamente para a instância referenciada.Se você usasse
getBean(MyBeanB.class)
novamente, o Spring retornaria uma nova instância, pois a definição de bean é para um protótipo de bean . Você não está fazendo isso, portanto, todas as invocações de métodos vão para o mesmo objeto.No seu segundo exemplo,
você declara um proxy com escopo implementado por meio do cglib. Ao solicitar um bean desse tipo do Spring com
O Spring sabe que
MyBeanB
é um proxy com escopo definido e, portanto, retorna um objeto proxy que satisfaz a API deMyBeanB
(ou seja, implementa todos os seus métodos públicos) que sabe internamente como recuperar um bean de tipo realMyBeanB
para cada chamada de método.Tente correr
Isso retornará
true
sugerindo que o Spring está retornando um objeto proxy singleton (não um protótipo de bean).Em uma chamada de método, dentro da implementação do proxy, o Spring usará uma
getBean
versão especial que sabe como distinguir entre a definição de proxy e aMyBeanB
definição de bean real . Isso retornará uma novaMyBeanB
instância (já que é um protótipo) e o Spring delegará a chamada do método a ela por meio de reflexão (clássicaMethod.invoke
).Seu terceiro exemplo é essencialmente o mesmo que o seu segundo.
fonte
context.getBean(MyBeanB.class)
, na verdade você não está obtendo o proxy, está obtendo o bean real.@Autowired
obtém o proxy (na verdade, falhará se você injetar emMyBeanB
vez do tipo de interface). Não sei por que o Spring permite fazergetBean(MyBeanB.class)
com INTERFACES.@Transactional
. Com@Autowired MyBeanBInterface
proxies e com escopo definido, o Spring injeta o objeto proxy. Se você apenas fizergetBean(MyBeanB.class)
isso, o Spring não retornará o proxy, ele retornará o bean de destino.