Wie wir wissen, verwendet Spring Proxys, um Funktionen hinzuzufügen ( @Transactional
und @Scheduled
zum Beispiel). Es gibt zwei Möglichkeiten: Verwenden eines dynamischen JDK-Proxys (die Klasse muss nicht leere Schnittstellen implementieren) oder Generieren einer untergeordneten Klasse mithilfe des CGLIB-Codegenerators. Ich habe immer gedacht, dass ich mit proxyMode zwischen einem dynamischen JDK-Proxy und CGLIB wählen kann.
Aber ich konnte ein Beispiel erstellen, das zeigt, dass meine Annahme falsch ist:
Fall 1:
Singleton:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
public void foo() {
System.out.println(myBeanB.getCounter());
}
public MyBeanB getMyBeanB() {
return myBeanB;
}
}
Prototyp:
@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;
}
}
Main:
MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());
Ausgabe:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
Hier können wir zwei Dinge sehen:
MyBeanB
wurde nur einmal instanziiert .- Um die
@Transactional
Funktionalität für hinzuzufügenMyBeanB
, verwendete Spring CGLIB.
Fall 2:
Lassen Sie mich die MyBeanB
Definition korrigieren :
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
In diesem Fall lautet die Ausgabe:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
Hier können wir zwei Dinge sehen:
MyBeanB
wurde 3 mal instanziiert .- Um die
@Transactional
Funktionalität für hinzuzufügenMyBeanB
, verwendete Spring CGLIB.
Können Sie erklären, was los ist? Wie funktioniert der Proxy-Modus wirklich?
PS
Ich habe die Dokumentation gelesen:
/**
* 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
*/
aber es ist mir nicht klar.
Aktualisieren
Fall 3:
Ich habe einen weiteren Fall untersucht, in dem ich die Schnittstelle extrahiert habe aus 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 {
und in diesem Fall ist die Ausgabe:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
Hier können wir zwei Dinge sehen:
MyBeanB
wurde 3 mal instanziiert .- Um die
@Transactional
Funktionalität für hinzuzufügenMyBeanB
, verwendete Spring einen dynamischen JDK-Proxy.
MyBeanB
Klasse keine Schnittstellen, sodass es nicht verwunderlich ist, dass in Ihrem Konsolenprotokoll CGLIB-Proxy-Instanzen angezeigt werden. In Fall 3, in dem Sie eine Schnittstelle einführen und implementieren, erhalten Sie folglich einen JDK-Proxy. Sie beschreiben dies sogar in Ihrem Einführungstext.<aop:config proxy-target-class="true">
bzw. konfiguriert@EnableAspectJAutoProxy(proxyTargetClass = true)
.Antworten:
Der für das
@Transactional
Verhalten generierte Proxy dient einem anderen Zweck als die Proxys mit Gültigkeitsbereich.Der
@Transactional
Proxy umschließt die spezifische Bean, um das Sitzungsverwaltungsverhalten hinzuzufügen. Alle Methodenaufrufe führen die Transaktionsverwaltung vor und nach der Delegierung an die eigentliche Bean durch.Wenn Sie es veranschaulichen, würde es so aussehen
Für unsere Zwecke können Sie das Verhalten im Wesentlichen ignorieren (entfernen
@Transactional
und Sie sollten dasselbe Verhalten sehen, außer Sie haben keinen cglib-Proxy).Der
@Scope
Proxy verhält sich anders. In der Dokumentation heißt es:Was Spring wirklich tut, ist die Erstellung einer Singleton-Bean-Definition für einen Factory-Typ, der den Proxy darstellt. Das entsprechende Proxy-Objekt fragt jedoch bei jedem Aufruf den Kontext für die tatsächliche Bean ab.
Wenn Sie es veranschaulichen, würde es so aussehen
Da
MyBeanB
es sich um eine Prototyp-Bean handelt, gibt der Kontext immer eine neue Instanz zurück.Nehmen Sie für die Zwecke dieser Antwort an, dass Sie die
MyBeanB
direkt mit abgerufen habenDies ist im Wesentlichen das, was Spring tut, um ein
@Autowired
Injektionsziel zu erreichen.In Ihrem ersten Beispiel
Sie deklarieren eine Prototyp-Bean- Definition (durch die Anmerkungen).
@Scope
hat einproxyMode
Element, dasSpring erstellt also keinen Proxy mit Gültigkeitsbereich für die resultierende Bean. Sie holen diese Bohne mit ab
Sie haben jetzt einen Verweis auf ein neues
MyBeanB
Objekt, das von Spring erstellt wurde. Wie bei jedem anderen Java-Objekt werden Methodenaufrufe direkt an die referenzierte Instanz gesendet.Bei
getBean(MyBeanB.class)
erneuter Verwendung würde Spring eine neue Instanz zurückgeben, da die Bean-Definition für eine Prototyp-Bean gilt . Sie tun das nicht, also gehen alle Ihre Methodenaufrufe an dasselbe Objekt.In Ihrem zweiten Beispiel
Sie deklarieren einen Proxy mit Gültigkeitsbereich, der über cglib implementiert wird. Wenn Sie eine Bohne dieses Typs von Spring with anfordern
Spring weiß, dass
MyBeanB
es sich um einen Proxy mit Gültigkeitsbereich handelt, und gibt daher ein Proxy-Objekt zurück, das die API von erfülltMyBeanB
(dh alle öffentlichen Methoden implementiert), das intern weiß, wie eine tatsächliche Bean vom TypMyBeanB
für jeden Methodenaufruf abgerufen wird .Versuche zu rennen
Dies gibt einen
true
Hinweis darauf zurück, dass Spring ein Singleton-Proxy-Objekt (kein Prototyp-Bean) zurückgibt.Bei einem Methodenaufruf verwendet Spring innerhalb der Proxy-Implementierung eine spezielle
getBean
Version, die zwischen der Proxy-Definition und der tatsächlichenMyBeanB
Bean-Definition zu unterscheiden weiß . Dadurch wird eine neueMyBeanB
Instanz zurückgegeben (da es sich um einen Prototyp handelt), und Spring delegiert den Methodenaufruf durch Reflektion (klassischMethod.invoke
) an diese Instanz .Ihr drittes Beispiel ist im Wesentlichen dasselbe wie Ihr zweites.
quelle
context.getBean(MyBeanB.class)
, erhalten Sie nicht den Proxy, sondern die eigentliche Bean.@Autowired
Ruft den Proxy ab (tatsächlich schlägt er fehl, wenn SieMyBeanB
anstelle des Schnittstellentyps injizieren ). Ich weiß nicht, warum Spring dichgetBean(MyBeanB.class)
mit INTERFACES machen lässt.@Transactional
. Mit@Autowired MyBeanBInterface
Proxys mit und ohne Gültigkeitsbereich injiziert Spring das Proxy-Objekt. Wenn Sie diesgetBean(MyBeanB.class)
jedoch nur tun , gibt Spring den Proxy nicht zurück, sondern die Ziel-Bean.