Come sappiamo, Spring usa i proxy per aggiungere funzionalità ( @Transactional
e @Scheduled
per esempio). Esistono due opzioni: utilizzare un proxy dinamico JDK (la classe deve implementare interfacce non vuote) o generare una classe figlio utilizzando il generatore di codice CGLIB. Ho sempre pensato che proxyMode mi permettesse di scegliere tra un proxy dinamico JDK e CGLIB.
Ma sono stato in grado di creare un esempio che dimostra che la mia ipotesi è sbagliata:
Caso 1:
Singleton:
@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;
}
}
Principale:
MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());
Produzione:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
Qui possiamo vedere due cose:
MyBeanB
è stato istanziato una sola volta .- Per aggiungere la
@Transactional
funzionalità perMyBeanB
, Spring ha usato CGLIB.
Caso 2:
Vorrei correggere la MyBeanB
definizione:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
In questo caso l'output è:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
Qui possiamo vedere due cose:
MyBeanB
è stato istanziato 3 volte.- Per aggiungere la
@Transactional
funzionalità perMyBeanB
, Spring ha usato CGLIB.
Potresti spiegare cosa sta succedendo? Come funziona davvero la modalità proxy?
PS
Ho letto la documentazione:
/**
* 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
*/
ma non è chiaro per me.
Aggiornare
Caso 3:
Ho studiato un altro caso, in cui ho estratto l'interfaccia da 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 in questo caso l'output è:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
Qui possiamo vedere due cose:
MyBeanB
è stato istanziato 3 volte.- Per aggiungere la
@Transactional
funzionalità perMyBeanB
, Spring ha usato un proxy dinamico JDK.
MyBeanB
classe non estende alcuna interfaccia, quindi non sorprende che il tuo log della console mostri le istanze proxy CGLIB. Nel caso 3 si introduce e implementa un'interfaccia, di conseguenza si ottiene un proxy JDK. Lo descrivi anche nel tuo testo introduttivo.
<aop:config proxy-target-class="true">
o @EnableAspectJAutoProxy(proxyTargetClass = true)
, rispettivamente.