Comme nous le savons, Spring utilise des proxys pour ajouter des fonctionnalités ( @Transactional
et @Scheduled
par exemple). Il existe deux options: utiliser un proxy dynamique JDK (la classe doit implémenter des interfaces non vides) ou générer une classe enfant à l'aide du générateur de code CGLIB. J'ai toujours pensé que proxyMode me permet de choisir entre un proxy dynamique JDK et CGLIB.
Mais j'ai pu créer un exemple qui montre que mon hypothèse est fausse:
Cas 1:
Singleton:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
public void foo() {
System.out.println(myBeanB.getCounter());
}
public MyBeanB getMyBeanB() {
return myBeanB;
}
}
Prototype:
@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());
Production:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
Ici, nous pouvons voir deux choses:
MyBeanB
n'a été instanciée qu'une seule fois .- Pour ajouter la
@Transactional
fonctionnalité deMyBeanB
, Spring a utilisé CGLIB.
Cas 2:
Permettez-moi de corriger la MyBeanB
définition:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
Dans ce cas, la sortie est:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
Ici, nous pouvons voir deux choses:
MyBeanB
a été instancié 3 fois.- Pour ajouter la
@Transactional
fonctionnalité deMyBeanB
, Spring a utilisé CGLIB.
Pourriez-vous expliquer ce qui se passe? Comment fonctionne réellement le mode proxy?
PS
J'ai lu la documentation:
/**
* 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
*/
mais ce n'est pas clair pour moi.
Mise à jour
Cas 3:
J'ai enquêté sur un autre cas, dans lequel j'ai extrait l'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 {
et dans ce cas la sortie est:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
Ici, nous pouvons voir deux choses:
MyBeanB
a été instancié 3 fois.- Pour ajouter la
@Transactional
fonctionnalité deMyBeanB
, Spring a utilisé un proxy dynamique JDK.
MyBeanB
classe n'extend aucune interface, il n'est donc pas surprenant que le journal de votre console affiche des instances de proxy CGLIB. Dans le cas 3, vous introduisez et implémentez une interface, vous obtenez donc un proxy JDK. Vous décrivez même cela dans votre texte d'introduction.
<aop:config proxy-target-class="true">
ou @EnableAspectJAutoProxy(proxyTargetClass = true)
, respectivement.