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.Réponses:
Le proxy généré pour le
@Transactional
comportement sert un objectif différent de celui des proxys de portée.Le
@Transactional
proxy est celui qui encapsule le bean spécifique pour ajouter un comportement de gestion de session. Toutes les invocations de méthode effectueront la gestion des transactions avant et après la délégation au bean réel.Si vous l'illustrez, cela ressemblerait à
Pour nos besoins, vous pouvez essentiellement ignorer son comportement (supprimer
@Transactional
et vous devriez voir le même comportement, sauf que vous n'aurez pas le proxy cglib).Le
@Scope
proxy se comporte différemment. La documentation indique:Ce que fait réellement Spring, c'est la création d'une définition de bean singleton pour un type de fabrique représentant le proxy. Cependant, l'objet proxy correspondant interroge le contexte du bean réel pour chaque appel.
Si vous l'illustrez, cela ressemblerait à
Puisqu'il
MyBeanB
s'agit d'un prototype de bean, le contexte retournera toujours une nouvelle instance.Aux fins de cette réponse, supposons que vous ayez récupéré
MyBeanB
directement lequi est essentiellement ce que Spring fait pour satisfaire une
@Autowired
cible d'injection.Dans votre premier exemple,
Vous déclarez une définition de bean prototype (via les annotations).
@Scope
a unproxyMode
élément quiSpring ne crée donc pas de proxy de portée pour le bean résultant. Vous récupérez ce bean avec
Vous avez maintenant une référence à un nouvel
MyBeanB
objet créé par Spring. C'est comme tout autre objet Java, les invocations de méthode iront directement à l'instance référencée.Si vous l'utilisiez à
getBean(MyBeanB.class)
nouveau, Spring retournerait une nouvelle instance, car la définition du bean est pour un bean prototype . Vous ne faites pas cela, donc toutes vos invocations de méthode vont au même objet.Dans votre deuxième exemple,
vous déclarez un proxy de portée implémenté via cglib. Lorsque vous demandez un bean de ce type à Spring avec
Spring sait qu'il
MyBeanB
s'agit d'un proxy de portée et retourne donc un objet proxy qui satisfait l'API deMyBeanB
(c'est-à-dire implémente toutes ses méthodes publiques) qui sait en interne comment récupérer un bean de type réelMyBeanB
pour chaque appel de méthode.Essayez de courir
Cela renverra une
true
allusion au fait que Spring retourne un objet proxy singleton (pas un bean prototype).Lors d'un appel de méthode, à l'intérieur de l'implémentation du proxy, Spring utilisera une
getBean
version spéciale qui sait faire la distinction entre la définition de proxy et laMyBeanB
définition de bean réelle . Cela retournera une nouvelleMyBeanB
instance (puisqu'il s'agit d'un prototype) et Spring lui déléguera l'invocation de la méthode par réflexion (classiqueMethod.invoke
).Votre troisième exemple est essentiellement le même que votre second.
la source
context.getBean(MyBeanB.class)
, vous n'obtenez pas réellement le proxy, vous obtenez le bean réel.@Autowired
obtient le proxy (en fait, il échouera si vous injectez à laMyBeanB
place du type d'interface). Je ne sais pas pourquoi Spring vous laisse fairegetBean(MyBeanB.class)
avec INTERFACES.@Transactional
. Avec des@Autowired MyBeanBInterface
proxys étendus et limités, Spring injectera l'objet proxy. Si vous le faitesgetBean(MyBeanB.class)
cependant, Spring ne renverra pas le proxy, il renverra le bean cible.