[Quellcode-Analyse] Zirkuläre Abhängigkeiten von Spring (Setter-Injektion, Konstruktor-Injektion, mehrere Instanzen, AOP)

vorne geschrieben

Zunächst einmal ist die einfachste Demo der zirkulären Abhängigkeit: A->B und B->A. Dieser Artikel konzentriert sich auf dieses Beispiel, um von Settern injizierte zirkuläre Abhängigkeiten, von Konstruktoren injizierte zirkuläre Abhängigkeiten, zirkuläre Abhängigkeiten mehrerer Instanzen und zirkuläre Abhängigkeiten mit AOP zu erläutern. Hier einige Schlussfolgerungen:

  • Spring kann nicht alle zyklischen Abhängigkeiten auflösen, beispielsweise die von Konstruktoren injizierten.
  • Spring verlässt sich darauf, frühe Objekte im Voraus verfügbar zu machen, um zyklische Abhängigkeiten aufzulösen, was durch Caching der Stufe drei erreicht wird.
  • Die dreistufigen Cache-Pools heißen im Quellcode „singletonObjects“, „earlySingletonObjects“ und „singletonFactories“.
  • Im Frühjahr werden alle Objekte über getBean abgerufen, das heißt, sie befinden sich im Cache der dritten Ebene. Wenn vorhanden, werden sie direkt aus dem Cache-Pool abgerufen und zurückgegeben. Wenn nicht, werden sie erstellt.
  • Die Objekterstellung in Spring ist grob in die folgenden Schritte unterteilt:
    • Konstruktor auswählen
    • Instanziieren Sie ein Objekt durch Reflexion
    • Platzieren Sie das instanziierte leere Objekt im Cache-Pool der dritten Ebene (singletonFactories).
    • Weisen Sie den Eigenschaften instanziierter leerer Objekte Werte zu (Abhängigkeitsinjektion).
    • Führen Sie einige Initialisierungsmethoden aus
    • Legen Sie die erstellten Objekte in den Cache-Pool der ersten Ebene (singletonObjects)
  • Beim Zuweisen von Eigenschaften zu einem Objekt wird eine Setter-Injektion durchgeführt. Die Injektionsmethode besteht darin, das Objekt über getBean abzurufen.
  • Die Konstruktorinjektion (falls vorhanden) wird während der Instanziierungsphase durchgeführt, und die Injektionsmethode besteht darin, das Objekt über getBean abzurufen.
  • Die Reihenfolge, in der Objekte erstellt werden, bestimmt, dass Spring von Settern injizierte zyklische Abhängigkeiten auflösen kann, von Konstruktoren injizierte zyklische Abhängigkeiten jedoch nicht auflösen kann.
  • Wenn eine zirkuläre Abhängigkeit auftritt, die Spring nicht auflösen kann, erkennt Spring sie anhand einiger Markierungen und löst eine Ausnahme aus, um das Programm zu beenden.
  • Der Cache der dritten Ebene speichert ObjectFactory anstelle von Object. Wenn ObjectFactory getObject aufgerufen wird, führt es getEarlySingleton() aus, um das Objekt zu initialisieren und zurückzugeben.
  • Unabhängig davon, ob eine zirkuläre Abhängigkeit auftritt, wird das instanziierte Objekt im Cache-Pool der dritten Ebene abgelegt, da nicht bekannt ist, ob beim Ablegen im Cache-Pool der dritten Ebene eine zirkuläre Abhängigkeit auftritt.
  • Wenn keine zirkuläre Abhängigkeit auftritt, wird der Cache der dritten Ebene nicht zum Initialisieren von Objekten verwendet, und der Cache der zweiten Ebene wird nicht verwendet.
  • Nur wenn eine zirkuläre Abhängigkeit auftritt, wird das Objekt über die BeanFactory des Caches der dritten Ebene initialisiert und in den Cache-Pool der zweiten Ebene gestellt.
  • Der Cache der zweiten Ebene kann je nach Ergebnis von ObjectFactory.getObject() das Originalobjekt oder das Proxy-Objekt sein.
  • Das Vorhandensein des Cache-Pools der dritten Ebene dient dazu, die AOP-Operation von A im Voraus abzuwickeln, wenn zirkuläre Abhängigkeiten auftreten, sodass B korrekt auf das Proxy-Objekt von A verweisen kann.
  • Der Cache der ersten Ebene speichert erstellte Objekte.

Das Kernproblem der zirkulären Abhängigkeit besteht darin, dass getBean unendlich verschachtelt aufgerufen wird, ohne ihn zu beeinträchtigen.

  • getBean(A)
    • getBean(B)
      • getBean(A)
        • getBean(B)
          • getBean(A)
            • …………

Um zu verstehen, warum Spring bestimmte zirkuläre Abhängigkeiten lösen kann, muss man daher tatsächlich verstehen, warum Spring in einigen Fällen die unendliche Verschachtelung von getBean verhindern kann.

getBean-Logik

Wenn Sie in den Quellcode klicken, können Sie sehen, dass getBean eigentlich nur ein „Stellvertreter“ ist.

	@Override
	public Object getBean(String name) throws BeansException {
    
    
		return doGetBean(name, null, null, false);
	}

	@Override
	public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
    
    
		return doGetBean(name, requiredType, null, false);
	}

	@Override
	public Object getBean(String name, Object... args) throws BeansException {
    
    
		return doGetBean(name, null, args, false);
	}

getBean ist eine sehr wichtige Methode in Spring. Sie verfügt über mehrere Überladungen. Wie doGetBean aufgerufen wird, wird anhand der Parameter bestimmt. Der doGetBean-Code ist zu lang und wird nicht veröffentlicht. Der optimierte Prozess ist wie folgt: Diese Methode ruft getSingleton hauptsächlich zweimal auf
Fügen Sie hier eine Bildbeschreibung ein
.Dieser getSingleton ist auch eine Methode mit mehreren Überladungen. Der erste getSingleton ist die Logik zum Überprüfen des Caches und der zweite getSingleton ist die Erstellungslogik. Durch den aufeinanderfolgenden Aufruf dieser beiden getSingletons wird die oben erwähnte Logik von getBean verwirklicht: Zurückgeben, wenn einer vorhanden ist, und Erstellen, wenn keiner vorhanden ist.
Natürlich verfügt Spring's Bean nicht nur über einen Singleton-Typ, sondern auch über mehrere Instanztypen und andere Typen. Der obige Prozess basiert auf der Voraussetzung, dass das erstellte Objekt ein Singleton ist. Tatsächlich verfügt doGetBean auch über die Logik, wie mit Multi-Instanz-Bean-Typen und anderen Bean-Typen umgegangen wird. Ich werde hier vorerst nicht auf Details eingehen, ich werde später darüber sprechen.

getSingleton ruft die Cache-Logik ab

Schauen wir uns die Logik des ersten getSingleton genauer an:

	@Nullable // doGetBean 第一次调用 getSingleton 的是这个
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    
    
		// Quick check for existing instance without full singleton lock
		Object singletonObject = this.singletonObjects.get(beanName); //先检查单例缓存池有没有这个对象,一级缓存,有就直接返回
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    
     // 是否正在创建,没有正在创建的话就返回
			singletonObject = this.earlySingletonObjects.get(beanName); // 查看是否有缓存,二级缓存,早期单例池,有也直接返回
			if (singletonObject == null && allowEarlyReference) {
    
    
				synchronized (this.singletonObjects) {
    
     // 锁 争夺锁的条件: 无一级,正在创建标记,无二级,允许早期引用
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName); // 单例模式的双检,加锁之后再查一次一级缓存
					if (singletonObject == null) {
    
     // 经过双检,一级缓存确实没有这个bean
						singletonObject = this.earlySingletonObjects.get(beanName); // 单例模式的双检,加锁之后再查一次二级缓存
						if (singletonObject == null) {
    
    // 经过双检,二级缓存确实没有这个bean (可是这个锁锁的是一级而不是二级缓存,这样能达成双检的目的吗)
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);//三级缓存,单例工厂池
							if (singletonFactory != null) {
    
     //在三级缓存中存在
								singletonObject = singletonFactory.getObject();//获取三级缓存 (getEarlyBeanReference)
								this.earlySingletonObjects.put(beanName, singletonObject); // 放入二级缓存
								this.singletonFactories.remove(beanName); // 移除三级缓存
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

Die SingletonObjects, EarlySingletonObjects und SingletonFactories sind hier das, was wir den Cache der dritten Ebene nennen. Zusätzlich zum Cache der dritten Ebene gibt es zwei Variablen, die sich auf die Logik zum Abrufen des Caches auswirken: AllowEarlyReference und IsSingletonCurrentlyInCreation (innerhalb der Methode wird überprüft, ob die Sammlung SingletonsCurrentlyInCreation diesen BeanNamen hat), wobei AllowEarlyReference ein konfigurierbares Attribut von Spring
ist Der Standardwert ist true. Das heißt, frühe Referenzen sind zulässig. Nur wenn true, kann Spring einige zirkuläre Abhängigkeiten auflösen. SingletonsCurrentlyInCreation ist eigentlich eine Markierung. Die Markierung wird vor und nach der Erstellung des Objekts markiert und entfernt. Dies geschieht insbesondere im zweiten getSingleton, auf das später noch eingegangen wird.
Dieser Code ist in vielen Schichten verschachtelt und scheint bei der Verwendung ein Problem zu sein. Die Pseudocode-Übersetzung lautet:

if(不存在一级缓存 && 对象没有被标记为正在创建){
    
    
	if(不存在二级缓存&&允许早期引用){
    
    
		synchronized(一级缓存池){
    
    
			if(不存在一级缓存){
    
    
				if(不存在二级缓存){
    
    
					if(存在三级缓存){
    
    
						将三级缓存移动到二级缓存
					}
				}
			}
		}
	}
}
返回对象

Zusammenfassend lässt sich sagen, dass die Logik lautet

  • Wenn ein Cache der ersten Ebene vorhanden ist, wird dieser direkt zurückgegeben
  • Wenn es nicht erstellt wird, kehren Sie direkt zurück (nur wenn dieses Objekt erstellt wird, ist es möglich, auf den Cache der zweiten Ebene und den Cache der dritten Ebene zuzugreifen).
  • Wenn ein Cache der zweiten Ebene vorhanden ist, kehren Sie direkt zurück
  • Wenn Verweise auf frühe Objekte nicht zulässig sind, kehren Sie direkt zurück (nur wenn Verweise auf frühe Objekte auf den Cache der zweiten und dritten Ebene zugreifen dürfen).
  • Wenn ein Cache der ersten Ebene vorhanden ist, wird dieser direkt zurückgegeben
  • Wenn ein Cache der zweiten Ebene vorhanden ist, kehren Sie direkt zurück
  • Wenn ein Cache der dritten Ebene vorhanden ist, legen Sie ihn zuerst in den Cache der zweiten Ebene und kehren Sie dann zurück

Beachten Sie, dass der Cache der ersten Ebene und der Cache der zweiten Ebene zweimal abgefragt und überprüft werden. Wenn Sie den Singleton-Modus verstehen, ist es leicht zu erkennen, dass hier eine doppelte Überprüfungsoperation verwendet wird. Die letzten beiden Überprüfungen dienen dazu, sicherzustellen, dass das Objekt muss ein Singleton sein. von. Um uns zu helfen, die Logik dieses Codes zu verstehen, betrachten wir den Fall von Multithreading nicht. Nach der Konvertierung in eine Single-Threaded-Logik lautet:

if(不存在一级缓存 && 对象没有被标记为正在创建){
    
    
	if(不存在二级缓存 && 允许早期引用){
    
    
		if(存在三级缓存){
    
    
			将三级缓存移动到二级缓存
		}
	}
}
返回对象

Wenn nicht einmal ein Cache der dritten Ebene vorhanden ist, wird null direkt zurückgegeben und die Logik zum Erstellen des Objekts wird später ausgeführt.
Selbst wenn es bis zu diesem Punkt vereinfacht wurde, sind Sie wahrscheinlich verwirrt und haben viele Zweifel: Warum werden drei Caches entworfen? Was ist der Unterschied? Warum ist die Urteilsordnung so? Warum den Cache der dritten Ebene in den Cache der zweiten Ebene verschieben? Usw. usw.
Diese werden im Folgenden einzeln beantwortet. Zunächst können Sie sich einen groben Eindruck von diesem Prozess machen.

Das Detailliertere ist, dass die synchronisierte Sperre hier der Objektmonitor des Cache-Pools der ersten Ebene ist, sodass die Granularität der Sperre kleiner ist als die des Klassenmonitors. Schließlich hat die Klasse, in der sich diese Methode befindet, mehr mehr als 600 Zeilen und viele Methoden verwenden Sperren. Der Vorteil der Verwendung des Objektmonitors des Cache-Pools der ersten Ebene als Sperre besteht darin, dass er logisch ist, die Parallelität verbessert und die Möglichkeit eines Deadlocks verringert.

getSingleton erstellt Objektlogik

Wenn getSingleton die Logik zum Erstellen eines Objekts zum zweiten Mal ausführt, führt es hauptsächlich nur vier Dinge aus:

  • Markieren Sie, dass dieses Objekt erstellt wird (singletonsCurrentlyInCreation.add(beanName))
  • Rufen Sie singletonFactory.getObject() auf, um ein Objekt zu erstellen
  • Heben Sie die Markierung auf, dass das Objekt erstellt wird (singletonsCurrentlyInCreation.remove(beanName))
  • Löschen Sie das Objekt aus den Cache-Pools der zweiten und dritten Ebene und fügen Sie es dem Cache-Pool der ersten Ebene hinzu.

singletonFactory ist ein Objekt vom Typ ObjectFactory. Diese sogenannte ObjectFactory ist eine funktionale Schnittstelle:

@FunctionalInterface
public interface ObjectFactory<T> {
    
    
	T getObject() throws BeansException;
}

Als ich das sehe, bin ich etwas verwirrt. Die getObject-Methode hat keine Parameter und die ObjectFactory hat keine Mitgliedsvariablen. Woher weiß sie, welches Objekt ich erstellen möchte? Wenn wir den Ursprung dieses Objekts verfolgen, können wir feststellen, dass diese singletonFactory ein Eingabeparameter von getSingleton ist. Wenn doGetBean getSingleton zum zweiten Mal aufruft (d. h. dieses Mal), übergeben wir einen Lambda-Ausdruck.

sharedInstance = getSingleton(beanName, () -> {
    
    
	try {
    
    
		return createBean(beanName, mbd, args); // 创建对象的实例
	}
	catch (BeansException ex) {
    
    
		// Explicitly remove instance from singleton cache: It might have been put there
		// eagerly by the creation process, to allow for circular reference resolution.
		// Also remove any beans that received a temporary reference to the bean.
		destroySingleton(beanName);
		throw ex;
	}
});

Dies beinhaltet das Konzept des Abschlusses. Die Definition des Abschlusses ist: ein auszuführender Codeblock und eine Computerumgebung , die die Bindung für freie Variablen bereitstellt . In Java ist ein Lambda-Ausdruck ein Abschluss. Ein Lambda-Ausdruck besteht aus drei Teilen:

  1. Codeblock
  2. Parameter
  3. freier Variablenwert

Die sogenannten freien Variablen sind: keine Parameter, nicht im Codeblock des Lambda-Ausdrucks definiert, in keinem globalen Kontext definiert, sondern lokale Variablen, die in der Umgebung definiert sind, in der der Lambda-Ausdruck definiert ist. (In diesem Fall sind es die lokalen Variablen von doCreateBean, nämlich beanName, mbd, args)

Der Lambda-Ausdruck muss den Wert der freien Variablen speichern. Dieser Vorgang wird als „Die freie Variable wird vom Lambda-Ausdruck erfasst“ bezeichnet.
Captures weisen eine wichtige Einschränkung auf: In Lambda-Ausdrücken können Sie nur auf den Wert verweisen und die Variable nicht ändern. Andernfalls führt die gleichzeitige Ausführung zu Thread-Unsicherheit. Darüber hinaus ist es illegal, in einem Lambda-Ausdruck auf eine Variable zu verweisen, die sich extern ändern kann. Daher können Lambda-Ausdrücke nur auf Variablen verweisen, die explizit oder implizit als final deklariert sind.

Das ist zu weit.

Was ich hier ausdrücken möchte, ist: Obwohl ObjectFactory keine Mitgliedsvariablen und getObject keine Eingabeparameter hat, übergeben wir hier einen Lambda-Ausdruck. Aufgrund seiner Abschlusseigenschaften kann Spring die von uns benötigten Beans korrekt erstellen.

createBean und doCreateBean erstellen Objekte

getObject erstellt Objekte über createBean. Der Hauptprozess von createBean ist:

  • Bereiten Sie Bean-Definitionsinformationen vor (einschließlich Informationen wie Umfang).
  • Rufen Sie doCreateBean auf, um ein Objekt zu erstellen

(Wann wird diese Verschachtelung der Schichten ein Ende haben?)

Der Hauptprozess von doCreateBean ist:

  • Konstruktor auswählen
  • Instanziieren Sie ein Objekt durch Reflexion
  • Platzieren Sie das instanziierte leere Objekt im Cache-Pool der dritten Ebene (singletonFactories).
  • Weisen Sie den Eigenschaften instanziierter leerer Objekte Werte zu (Abhängigkeitsinjektion).
  • Führen Sie einige Initialisierungsmethoden aus
  • Legen Sie die erstellten Objekte in den Cache-Pool der ersten Ebene (singletonObjects)

Nachdem wir Schicht für Schicht aufgerufen hatten, fanden wir schließlich eine Methode, die wirklich definiert, wie eine Bean erstellt wird!
Dieser Prozess stellt den wichtigsten Teil der Logik für das Problem der zirkulären Abhängigkeit dar. Dieser Prozess zeigt, warum Spring die zirkuläre Abhängigkeit des Setters lösen kann, aber warum er die zirkuläre Abhängigkeit des Konstruktors nicht lösen kann.

Wie löst Spring von Settern injizierte zirkuläre Abhängigkeiten?

Der Kern der Lösung der Setter-Zirkelabhängigkeit besteht darin, Bean-Objekten die Zuweisung unvollendeter Abhängigkeiten unter der Voraussetzung zu ermöglichen, dass jeder ein Singleton ist . Diese Abhängigkeiten existieren in den Caches der zweiten und dritten Ebene. Wenn Sie A erstellen, instanziieren Sie es zunächst mit dem Konstruktor ohne Parameter, legen Sie das nicht zugewiesene A in den Cache der dritten Ebene und führen Sie dann eine Attributzuweisung durch (Setter-Injektion in B). Da B zu diesem Zeitpunkt noch nicht erstellt wurde, wird es bei der Erstellung erstellt Wenn der Prozess ausgeführt wird, wird der Setter während der B-Attributzuweisungsphase in A eingefügt. Wenn A über die getBean-Methode abgerufen wird, wird festgestellt, dass sich A im Cache der dritten Ebene befindet, und der Erstellungsprozess von A wird nicht ausgeführt , sondern direkt in den Cache der dritten Ebene zurückgegeben. Die Zuweisung von B ist abgeschlossen und das initialisierte B wird an A zurückgegeben. Zu diesem Zeitpunkt wird auch A initialisiert. Da A und B beide Singletons sind, enthält B eine Referenz und das zuvor nicht zugewiesene A wird zum initialisierten A. Der vereinfachte Prozess ist wie folgt: Es ist ersichtlich, dass getBean(A) beim ersten Mal vor der Zuweisung des Attributs in den Cache der dritten Ebene gelegt wird und beim zweiten Mal getBean(A) getSingletonObject Der Cache kann abgerufen werden (natürlich, weil es zuvor als erstellt markiert wurde), wodurch die unendliche Verschachtelung beendet wird. Damit soll die zirkuläre Abhängigkeit gelöst werden.


Fügen Sie hier eine Bildbeschreibung ein

Warum kann Spring von Konstruktoren injizierte zirkuläre Abhängigkeiten nicht auflösen?

Wenn es sich um eine Konstruktorinjektion handelt, wird sie während der Instanziierungsphase injiziert, und die injizierte Methode erhält auch Abhängigkeiten durch Aufrufen von getBean. Da B sich jedoch vor dem Aufruf von getBean nicht selbst in den Cache-Pool einfügt, kann es den Cache der dritten Ebene nicht abrufen, wenn B getBean aufruft, um A abzurufen, sodass der Erstellungsprozess fortgesetzt wird. Wenn kein Eingriff durchgeführt wird, tritt eine Endlosschleife auf .
Fügen Sie hier eine Bildbeschreibung ein
Beachten! ! ! Die Implementierung von Spring wird nicht unendlich verschachtelt sein. Aufmerksame Leser können feststellen, dass das erste und das zweite getBean(A) zwar den Erstellungsprozess durchlaufen, ihre Urteile in getSingleton#1 jedoch unterschiedlich sind. Das heißt, das erste und das zweite getBean(A) sind nicht genau gleich, einige ihrer Zustände sind unterschiedlich! Spring verwendet diese Zustände, um zu identifizieren, welche zirkulären Abhängigkeiten nicht aufgelöst werden können, und löst so eine Ausnahme aus. Das heißt, die in der unteren rechten Ecke des Bildes gezeichnete Endlosschleife ist eine ausgelassene Zeichenmethode und wird tatsächlich nicht vorkommen.

Wie erkennt Spring von Konstruktoren eingefügte zirkuläre Abhängigkeiten?

Nach unserer Vorstellung sollte das Ergebnis einer zirkulären Abhängigkeit eine Endlosschleife sein, aber wenn wir einen Code mit zirkulärer Abhängigkeit schreiben, löst Spring offensichtlich eine Ausnahme aus, um das Programm zu beenden.

Unsatisfied dependency expressed through constructor parameter 0

Wo hat Spring diese Ausnahme ausgelöst? Wie hat er es erkannt und beurteilt?
Wenn eine vom Konstruktor injizierte zirkuläre Abhängigkeit auftritt, tritt A in den zweiten Erstellungsprozess ein, dh getSingleton wird zum zweiten Mal aufgerufen. Wie oben erwähnt, markiert getSingleton beim Erstellen eines Objekts zunächst, dass das Objekt erstellt wird ( singletonsCurrentlyInCreation) werfen wir einen Blick darauf, wie es im Detail implementiert wird:

	protected void beforeSingletonCreation(String beanName) {
    
     // 查看是否存在,不存在则添加。如果存在或者添加失败则抛异常(构造器注入循环依赖在此发现)
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    
    
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

Wie Sie sehen können, wird eine Ausnahme ausgelöst, wenn festgestellt wird, dass diese Bean als erstellt markiert wurde. Wenn getBean(A) zum zweiten Mal nach getSingleton ausgeführt wird, erkennt es zwangsläufig die Markierung, als der Prozess zum ersten Mal erstellt wurde, sodass eine Ausnahme ausgelöst wird und das Programm beendet wird.

Wie entdeckt Spring zirkuläre Abhängigkeiten von Multi-Instanz-Beans?

Die beiden oben erwähnten zyklischen Abhängigkeiten werden alle unter der Prämisse von Singletons diskutiert.
Mehrere Instanzen und Singletons sind tatsächlich ähnlich. Bei der Setter-Injektion können zirkuläre Abhängigkeiten aufgelöst werden, bei der Konstruktor-Injektion jedoch nicht. Da jedoch mehrere Instanzen die getSingleton-Methode nicht aufrufen, gibt es natürlich keine Markierung. Mit anderen Worten, egal wie oft eine Multi-Instanz-Bean erstellt wird, wird beforeSingletonCreation nicht aufgerufen, aber Spring kann es tatsächlich erkennen und einen Fehler melden:

Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

Wo löst das Multi-Instanz-Bean eine Ausnahme aus?
Unabhängig davon, ob es sich um eine einzelne Instanz oder um mehrere Instanzen handelt, wird dasselbe createBean aufgerufen. Die einzelne Instanz wird in getSingleton erkannt, der äußeren Schicht von createBean, sodass mehrere Instanzen natürlich in der äußeren Schicht von createBean erkannt werden , das doGetBean heißt
. Es gibt so einen Code vor getSingleton oder createBean:

if (isPrototypeCurrentlyInCreation(beanName)) {
    
    //如果是多实例bean,并且已经创建过了,就会判断为多实例循环依赖(构造器注入),抛异常
	throw new BeanCurrentlyInCreationException(beanName);
}
-------------------
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    
    
	Object curVal = this.prototypesCurrentlyInCreation.get();
	return (curVal != null &&
			(curVal.equals(beanName) || (curVal instanceof Set<?> set && set.contains(beanName))));
}

Wie der Name schon sagt, soll dieser Code die zirkuläre Abhängigkeit von Multi-Instanz-Beans lösen.
Wann wird diese Markierung manchmal vorgenommen? Natürlich ist es immer noch in doGetBean

else if (mbd.isPrototype()) {
    
     //多实例创建
	// It's a prototype -> create a new instance.
	Object prototypeInstance = null;
	try {
    
    
		beforePrototypeCreation(beanName);
		prototypeInstance = createBean(beanName, mbd, args);
	}
	finally {
    
    
		afterPrototypeCreation(beanName);
	}
	beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

Die Logik dieses Teils ist der Logik von getSingleton sehr ähnlich, dh unabhängig davon, ob es sich um eine einzelne Instanz oder um mehrere Instanzen handelt, wird sie markiert. Der Unterschied besteht darin, dass die Stelle, an der sie markiert wird, unterschiedlich ist.
Fügen Sie hier eine Bildbeschreibung ein

L3-Cache

Erinnern Sie sich noch an die oben beschriebene Erstellungslogik von getSingleton#1? Gemäß dem Erstellungsprozess legen wir das Objekt zunächst in den Cache der dritten Ebene und legen es dann aus dem Cache der dritten Ebene in den Cache der zweiten Ebene, wenn eine zirkuläre Abhängigkeit auftritt. Nachdem die Initialisierung abgeschlossen ist, legen wir das Objekt ab Cache der zweiten Ebene in den Cache der ersten Ebene. Es scheint etwas schwierig zu verstehen, warum dieses Objekt auf diese Weise bewegt wird, oder welche Konsequenzen es hat, wenn es nicht getan wird? Ich verwende beispielsweise nur den Cache der ersten Ebene und lege das Objekt nach der Instanziierung direkt darin ab. Wenn eine zirkuläre Abhängigkeit auftritt, kann sie auch im Cache der ersten Ebene gefunden werden. Dadurch wird auch die zirkuläre Abhängigkeit gelöst und viele Schritte eingespart . Diese Fragen werden im Folgenden beantwortet. Zu diesem Zeitpunkt können wir uns zunächst ansehen, wie der Cache der dritten Ebene im Quellcode definiert ist:

	/** Cache of singleton objects: bean name to bean instance.一级缓存 */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of early singleton objects: bean name to bean instance. 二级缓存 */
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

	/** Cache of singleton factories: bean name to ObjectFactory. 三级缓存 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

Wir werden überrascht sein, dass die Caches der ersten und zweiten Ebene unterschiedlich sind. Was im Cache der dritten Ebene gespeichert ist, ist kein Objekt, sondern ObjectFactory. Leser mit guten Erinnerungen erinnern sich vielleicht daran, dass wir oben erwähnt haben, dass der Parameter, den wir beim Aufruf von getSingleton#2 übergeben haben, ebenfalls eine ObjectFactory war. Tatsächlich ist Speicher zu diesem Zeitpunkt keine schlechte Sache, da die oben erwähnte ObjectFactory fast keine semantische Verbindung mit der hier im Cache der dritten Ebene gespeicherten ObjectFactory hat , mit der Ausnahme, dass es sich um dieselbe Klasse handelt und der Aufruf von getObject ein Objekt außerhalb erstellen kann . Mit anderen Worten: Es handelt sich um zwei Arten von Fabriken, die zwei unterschiedliche Objekte herstellen. Da es sich bei ObjectFactory lediglich um eine funktionale Schnittstelle handelt, kann dies nur bedeuten, dass der Lambda-Ausdruck, der sie implementiert, über bestimmte Funktionen verfügt. Da beide Klassen Comparable implementieren, besteht möglicherweise keine semantische Verbindung zwischen ihnen. Dies kann nur bedeuten, dass sie alle mit sich selbst verglichen werden können. Das Folgende ist die offizielle Beschreibung dieser Schnittstelle:

Definiert eine Factory, die beim Aufruf eine Objektinstanz (möglicherweise gemeinsam genutzt oder unabhängig) zurückgeben kann.
Diese Schnittstelle wird normalerweise verwendet, um eine generische Factory zu kapseln, die bei jedem Aufruf eine neue Instanz (Prototyp) eines Zielobjekts zurückgibt.
Diese Schnittstelle ähnelt FactoryBean, aber Implementierungen der letzteren sind normalerweise dazu gedacht, als SPI-Instanzen in einer BeanFactory definiert zu werden, während Implementierungen dieser Klasse normalerweise dazu gedacht sind, als API an andere Beans (durch Injektion) weitergeleitet zu werden. Daher weist die Methode getObject() ein anderes Verhalten bei der Ausnahmebehandlung auf.

Einfach ausgedrückt: Die Klasse, die diese Schnittstelle implementiert, bedeutet, dass sie eine API zum Erstellen von Objekten bereitstellen kann. Die von dieser API erstellten Objekte sind mehrere Instanzen, dh wie viele Objekte werden so oft generiert, wie die Fabrik aufgerufen wird. Wie oben erwähnt, wird diese API aufgerufen, wenn eine zirkuläre Abhängigkeit auftritt, um mehrere Instanzen zu generieren. Wird dies dazu führen, dass die Beans im Spring-Container keine Singletons sind? Natürlich nicht, erstens, denn wenn die zirkuläre Abhängigkeit aufgelöst werden kann, wird diese Schnittstelle nur einmal aufgerufen. Wenn es nicht gelöst werden kann, wird eine Ausnahme ausgelöst und das Programm wird beendet, bevor diese API zum zweiten Mal aufgerufen wird. Der zweite Grund ist, dass getSingleton, das diese API aufruft, eine doppelte Überprüfung verwendet, um sicherzustellen, dass diese API nur einmal aufgerufen wird. Da die Datenstruktur jeder Ebene des Cache-Pools schließlich eine Hash-Tabelle ist, stellt ihre einzigartige Schlüsselfunktion sicher, dass es nur eine Instanz eines beanNamens gibt.
(Auch hier gehe ich zu weit, haha, aber ich denke, das muss klargestellt werden)

Was wurde dem L3-Cache-Pool hinzugefügt?

Wie oben erwähnt, sind die ObjectFactory, die wir an getSingleton # 2 übergeben haben, und die zwischengespeicherte ObjectFactory der dritten Ebene zwei verschiedene Dinge. Wir wissen, dass erstere hauptsächlich eine createBean-Methode aufruft. Was ist also letztere?
Schauen Sie sich die Anweisung im Quellcode an, um den Level-3-Cache hinzuzufügen:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

Prägnant und klar: getEarlyBeanReference wird aufgerufen und die freien Variablen beanName, mbd und bean werden ebenfalls vom Lambda-Ausdruck erfasst. Wenn das oben an getSingleton#2 übergebene Lambda nicht die Vorteile des Schließens widerspiegelt, kann dieser Lambda-Ausdruck es dieses Mal deutlicher machen: Wenn der Codeblock ausgeführt werden soll, ist die freie Variable wahrscheinlich bereits nicht vorhanden Der Kontext oder wurde geändert. Um die von uns gewünschten Berechnungen korrekt durchzuführen, ist es besonders wichtig, eine Berechnungsumgebung zu haben, die diese freien Variablen erfassen kann. Die Kombination dieser Berechnungsumgebung und des Codeblocks ist ein Abschluss. In Java ist es ist ein Lambda-Ausdruck.
Was bewirkt diese Methode? Sein Code lautet wie folgt:

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    
    
		Object exposedObject = bean; // 为了让我们自己实现的后置处理器能增强bean,不直接放bean而是放工厂(lambda表达式)
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    
    
			// 如果有后置增强器就执行 (AutowiredAnnotationBeanPostProcessor 在此提供接口进行增强)
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
    
    
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject; // 否则返回原来的bean
	}

Hier wird eine Reihe von Postprozessoren aufgerufen. Zusätzlich zu einigen Postprozessoren, die mit Spring geliefert werden, gibt es hier einen zusätzlichen AnnotationAwareAspectJAutoProxyCreator, um einen Proxy für das Objekt zu generieren, wenn wir AOP aktivieren. Wenn getObject des Caches der dritten Ebene aufgerufen wird, um das Objekt abzurufen, wird AOP ausgeführt, um das Proxy-Objekt zu generieren.
Wann werden wir es also nennen? Leser mit gutem Gedächtnis können sich wahrscheinlich an das oben Gesagte erinnern. Leser mit schlechtem Gedächtnis können die globale Suche ausprobieren, was wirklich nützlich ist.
Fügen Sie hier eine Bildbeschreibung ein
Tatsächlich gibt es nicht viele Orte, an denen Spring diese Schnittstelle verwendet. Es gibt zwei Hauptorte. Abgesehen von den unterschiedlichen Kommentaren, die ich geschrieben habe, sind diese beiden Orte fast identisch. Sie gehören sogar zur selben Klasse, aber als Wie oben erwähnt, handelt es sich um zwei verschiedene Fabriken.

singletonObject = singletonFactory.getObject();//获取三级缓存 (getEarlyBeanReference)
this.earlySingletonObjects.put(beanName, singletonObject); // 放入二级缓存
this.singletonFactories.remove(beanName); // 移除三级缓存

Obwohl wir den Ort gefunden haben, an dem es aufgerufen wurde, haben wir die Frage, wann es aufgerufen werden soll, immer noch nicht beantwortet. Sie können sagen: getObject wird aufgerufen, wenn in getSingleton#1 „der Cache der dritten Ebene in den Cache der zweiten Ebene gelegt wird“.
Das stimmt, aber ich denke, eine bessere Antwort ist: Wenn eine zirkuläre Abhängigkeit auftritt, wird getBean(A) das zweite Mal aufgerufen.
Dieser Vorgang wird genau dann ausgeführt, wenn dies geschieht. Dies bedeutet höchstwahrscheinlich, dass dieser Teil des Codes für diesen Fall vorbereitet ist.

Warum einhunderttausend L3-Cache?

Wenn eine zirkuläre Abhängigkeit auftritt, legt getBean(A) beim zweiten Mal den Cache der dritten Ebene von A in den Cache-Pool der zweiten Ebene und gibt diesen Cache der zweiten Ebene zurück. Warum zum Cache der zweiten Ebene zurückkehren? Da eine zirkuläre Abhängigkeit auftritt, muss B eine Referenz auf A erhalten. Warum ist es notwendig, den Cache der dritten Ebene in den Cache der zweiten Ebene zu legen? Weil B anstelle einer Factory-Referenz eine Referenz auf A benötigt. Wäre es also nicht in Ordnung, direkt einen Verweis auf den Cache der zweiten Ebene (oder sogar der ersten Ebene) zu speichern? Nein, zusätzlich zur Rückgabe eines Verweises auf A führt der Cache der dritten Ebene auch eine Reihe von Nachbearbeitungen für A durch. B möchte nur einen Verweis auf A. Ob A eine Nachbearbeitung durchgeführt hat und ob die Initialisierung abgeschlossen ist, ist B egal. Egal wie sich A ändert oder wie er damit umgeht, B muss nur noch A finden. Danach Nachbearbeitung, B wird damit nicht klarkommen? Und es wurde ursprünglich später behandelt, warum musste es im Voraus erledigt werden?
Das Problem liegt hier. Die Anforderung von B besteht darin, A korrekt zu referenzieren. Im Allgemeinen scheint es kein Problem zu geben, wenn die Nachbearbeitung zuerst und dann ausgeführt wird, da die meisten Nachbearbeitungen die Referenz des Objekts nicht ändern. Aber wie bereits erwähnt, wenn AOP aktiviert ist, wird der AOP-Proxy hier ausgeführt. Der Proxy ist eine Referenz, und das Original A ist eine weitere Referenz. Denken Sie, dass B zu diesem Zeitpunkt die Proxy-Referenz enthalten sollte ? Oder ist es ein Hinweis auf A? Natürlich ist es eine Anspielung auf den Agenten. Wenn eine zirkuläre Abhängigkeit auftritt und A in B eingefügt wird, wird A erst in der Zuweisungsphase ausgeführt, und während der Initialisierungsphase von A wird eine Reihe von Nachverarbeitungen ausgeführt. Diese Nachverarbeitungen können Proxy-Objekte generieren. Wenn Sie möchten B injizieren Für dieses Proxy-Objekt können Sie diese Nachbearbeitung nur im Voraus durchführen und dann die initialisierte Referenz von A an B zurückgeben. Andernfalls kann Spring nicht garantieren, dass B zu diesem Zeitpunkt die richtige Referenz erhält.
Wenn wir hier direkt den Cache der zweiten Ebene (oder sogar des ersten Levels) verwenden, bedeutet dies, dass in diesem Schritt alle Beans den AOP-Proxy abschließen müssen. Dies widerspricht dem Design von Spring, AOP mit dem Bean-Lebenszyklus zu kombinieren: Lassen Sie die Bean den Proxy im letzten Schritt des Lebenszyklus abschließen, anstatt den Proxy unmittelbar nach der Instanziierung abzuschließen.

(100000=7)

Warum brauchen wir einen Cache der dritten Ebene?

Nun ist es einfach, diese Frage zu beantworten. Die drei Caches repräsentieren unterschiedliche Zustände eines Bean-Objekts:

  • Im Cache der ersten Ebene bedeutet dies, dass das Singleton-Objekt vollständig erstellt wurde und direkt verwendet werden kann.
  • Im Cache der zweiten Ebene befindet sich das Objekt zu diesem Zeitpunkt in einem frühen Offenlegungszustand, wodurch sichergestellt wird, dass seine endgültige Referenz dies ist, aber nicht garantiert wird, dass seine Attribute vollständig gefüllt und initialisiert sind.
  • Im Cache der dritten Ebene befindet sich das Objekt in der Post-Instanziierungsphase, wurde jedoch nicht frühzeitig verfügbar gemacht und wird möglicherweise später nicht verfügbar gemacht. Zu diesem Zeitpunkt gibt es keine Garantie dafür, dass seine Attribute vollständig gefüllt sind und die Initialisierung abgeschlossen ist.

Zu beachten ist, dass jedes Objekt nach der Instanziierung dem Cache der dritten Ebene hinzugefügt wird, auch wenn keine zirkuläre Abhängigkeit besteht, da Spring bisher nicht feststellen kann, ob diese Bean eine zirkuläre Abhängigkeitsbeziehung zu anderen Beans hat. Aber nur wenn tatsächlich eine zirkuläre Abhängigkeit auftritt, wird die ObjectFactory des Caches der dritten Ebene verwendet, um das Objekt im Voraus zu generieren, um sicherzustellen, dass andere Objekte korrekt auf sich selbst verweisen können. Andernfalls wird eine Factory erstellt und in die dritte Ebene verschoben Cache, aber nicht. Es durchläuft diese Fabrik, um das Objekt tatsächlich zu erstellen, und entfernt nach der Erstellung den Cache der dritten Ebene.
Wenn ein Cache der dritten Ebene vorhanden ist, sieht der Ausführungsprozess wie folgt aus:
Fügen Sie hier eine Bildbeschreibung ein
Wenn kein Cache der dritten Ebene vorhanden ist, sieht der Ausführungsprozess wie folgt aus: Die
Fügen Sie hier eine Bildbeschreibung ein
vorherige Ausführung des Postprozessors zur Initialisierung stellt einen Kompromiss dar und ist es nicht Für die meisten Beans erforderlich, daher wird Level-3-Cache verwendet. Gleichzeitig besteht ein weiterer Nachteil der Verwendung nur des Caches der ersten Ebene darin, dass es in einer Multithread-Umgebung zu Thread-Sicherheitsproblemen kommt. Eine noch nicht erstellte Bean wird im Cache abgelegt, und andere Threads erhalten möglicherweise eine unvollständige Bean. Infolgedessen hat das Programm unerwartete Ergebnisse.

Natürlich unterscheidet Spring gewöhnliche Bohnen und Fabrikbohnen durch Präfixe, sodass nur ein Behälter zur Lagerung beider Bohnensorten verwendet werden kann. Nach dieser Idee können wir tatsächlich Präfixe verwenden, um die drei Caches zu unterscheiden, sodass nur ein Cache ausreicht! Aber der Cache der dritten Ebene ist eigentlich ein logisches Konzept, kein Containerkonzept. Tatsächlich muss Spring möglicherweise häufig auf den Cache zugreifen, und das Zusammenführen der drei Caches in einem Container kann sich auch auf die Leistung des Systems auswirken.

Was passiert mit der Initialisierung von A unter der Schleife?

Zunächst ist zu beachten, dass in der getEarlyBeanReference-Methode zwar eine Reihe von Postprozessoren ausgeführt werden, dies jedoch nicht bedeutet, dass die Initialisierung abgeschlossen ist. Es gibt viele Arten von Postprozessoren, und hier wird nur der Postprozessor vom Typ smartInstantiationAware ausgeführt.

static class BeanPostProcessorCache {
    
    
	final List<InstantiationAwareBeanPostProcessor> instantiationAware = new ArrayList<>();
	final List<SmartInstantiationAwareBeanPostProcessor> smartInstantiationAware = new ArrayList<>();
	final List<DestructionAwareBeanPostProcessor> destructionAware = new ArrayList<>();
	final List<MergedBeanDefinitionPostProcessor> mergedDefinition = new ArrayList<>();
}

Es gibt mehr Arten von Postprozessoren als die oben aufgeführten. Selbst wenn A die Ausführung einiger Postprozessoren verkörpert, bedeutet dies daher nicht, dass die Initialisierung abgeschlossen ist, und seine Initialisierungsphase kann nicht ignoriert werden.

A hat in getEarlyBeanReference bereits einmal ein Proxy-Objekt erstellt. Nachdem B erstellt wurde, wird auch A initialisiert. Wird zu diesem Zeitpunkt ein weiteres Proxy-Objekt erstellt? Natürlich ist es unmöglich, es erneut zu erstellen.
AOP erstellt ein Proxy-Objekt über die Methode „wrapIfNecessary“ und führt während der Initialisierung die folgende Logik aus:

Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    
     // 如果之前已经创建了一次代理了,那么此时一定不相等,则不再进行代理,直接返回bean
	return wrapIfNecessary(bean, beanName, cacheKey); // 通过此方法创建代理对象
}

Wenn zuvor ein Proxy erstellt wurde, ist eine Markierung vorhanden. Wenn eine Markierung vorhanden ist, wird die Methode „wrapIfNecessary“ nicht mehr ausgeführt. Kehren Sie direkt zur ursprünglichen Bean zurück.
Das Problem tritt jedoch erneut auf: Nachdem A die Initialisierung durchgeführt hat, handelt es sich immer noch um ein Objekt ohne Proxy, aber das an die Funktion der oberen Ebene zurückgegebene Objekt wird im Cache der ersten Ebene gespeichert.

// .....
exposedObject = initializeBean(beanName, exposedObject, mbd);
// .....
return exposedObject;

Wäre das im Cache der ersten Ebene gespeicherte Objekt nicht ein Objekt ohne Proxy? Aber was gespeichert wird, sollte ein Proxy-Objekt sein. Nach der Initialisierung der Bean und vor der Rückkehr zu exponiertem Objekt (dh zwischen der zweiten Auslassung und dem Guten im obigen Code) gibt es eine solche logische Beurteilung

if (earlySingletonExposure) {
    
    
	// 尝试去拿缓存,但是禁用早期引用,也就说,三级缓存是拿不到的,但此时上面创建的实例也还没有放进一级缓存
	// 循环依赖时A被B从三级放到二级,这时可以拿到,也就是说这里拿到的都是那些被提前引用的Bean。
	Object earlySingletonReference = getSingleton(beanName, false);
	if (earlySingletonReference != null) {
    
    
		// bean 是实例化完时的引用
		// earlySingletonReference 是 二级缓存中的对象,如果被AOP代理了,这个就是代理对象,因为提前执行了AOP,B此时持有的也是这个对象
		// exposedObject 是初始化方法后的对象引用,但是他和bean一般还都是一样的,否则就是初始化的时候改变了引用,此时就会进入下面else if的逻辑当中
		if (exposedObject == bean) {
    
     // 看是否初始化之后引用发生了变化
			exposedObject = earlySingletonReference;
			// 这里的逻辑就是要返回一个对象,如果在getEarlyBeanReference前后bean对象没有被替换(代理),那么就这两个是同一个引用,直接暴露即可,否则返回的是代理对象
		}// 否则就要判断一下是否允许直接注入一个不由容器实例的对象
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    
    
			// .......
		}
	}
}

Wie Sie sehen, erhalten wir das Objekt, für das der Proxy zuvor ausgeführt wurde, und exposedObject = earlySingletonReference;aktualisieren dann den Rückgabewert, sodass die richtige Referenz im Cache der ersten Ebene platziert wird. Vielleicht möchten Sie fragen: Würde die von A ausgeführte Initialisierungsmethode nicht ungültig werden? Schließlich wurde das gesamte A durch das vorherige Proxy-Objekt ersetzt. Vergessen Sie nicht, dass das Proxy-Objekt intern einen Verweis auf A enthält, sodass A bei der Initialisierung gültig ist.

Zusammenfassen

Zu diesem Zeitpunkt habe ich die meisten Fragen zu den zirkulären Abhängigkeiten von Spring endlich fertig geschrieben. Nachdem ich dies geschrieben habe, wird mein Prinzip dieses Teils von Spring sehr klar, aber ich kann nicht garantieren, dass die Leser nach dem Lesen ein sehr klares Verständnis haben it. Letztendlich muss dieses Ding noch persönlich gelesen werden, um den Code zu erleben. Der Artikel kann nur einige Ideen und Einsichten liefern. Und da der Träger begrenzt ist, ist der Fragmentcode tatsächlich nicht auf das Verständnis des Programms beschränkt, und es wird definitiv ein wenig schwindelig sein. Er kann erst nach mehrmaliger Verfolgung klarer werden. Was den Tracking-Code betrifft, so verfügt IDEA zwar über einen Aufrufstapel, dieser geht jedoch schnell im riesigen Code verloren, wenn Sie den Stapel ständig öffnen und verschieben. Daher denke ich, dass Sie zusätzlich zur wiederholten Verfolgung auch eine Mindmap verwenden können, um eine ähnliche Flamme zu erstellen Diagramm Etwas, das dem Zeichnen einer Karte entspricht und dabei hilft, den Programmausführungsprozess zu verdeutlichen. Für den Code des zirkulären Abhängigkeitsteils habe ich eine solche Karte erstellt, auf die Sie sich beziehen können:
Fügen Sie hier eine Bildbeschreibung ein
Die
Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein
obige AOP-Karte lässt viele logische Urteile und Schritte aus und dient nur als Referenz.

Supongo que te gusta

Origin blog.csdn.net/weixin_45654405/article/details/127579128
Recomendado
Clasificación