Spring の循環依存関係の問題
循環依存問題とは何ですか
複数の Bean が相互に依存し、閉ループを形成します。例: A は B に依存し、B は C に依存し、C は A に依存します。
一般に、Spring コンテナ内の循環依存関係の問題を解決する方法を尋ねる場合は、デフォルトのシングルトン Bean 内でプロパティが相互に参照するシナリオを指している必要があります。つまり、Spring の循環依存関係は、Spring コンテナがインジェクションされるときに発生する問題です。
例
プロジェクトの構造
プロジェクトコード
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com</groupId>
<artifactId>spring-circular-dependency</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.apache.geronimo.bundles</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8_2</version>
</dependency>
</dependencies>
</project>
Bean01.java
package com.spring.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author honey
* @date 2023-08-23 17:50:53
*/
@Component
public class Bean01 {
@Autowired
private Bean02 bean02;
}
Bean02.java
package com.spring.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author honey
* @date 2023-08-23 17:52:55
*/
@Component
public class Bean02 {
@Autowired
private Bean01 bean01;
}
SpringConfig01.java
package com.spring.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author honey
* @date 2023-08-23 17:59:37
*/
@Configuration
@ComponentScan(value = {
"com.spring.bean"})
public class SpringConfig01 {
}
SpringTest01.java
package com.spring.test;
import com.spring.bean.Bean01;
import com.spring.bean.Bean02;
import com.spring.config.SpringConfig01;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author honey
* @date 2023-08-23 18:00:24
*/
public class SpringTest01 {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig01.class);
Bean01 bean01 = applicationContext.getBean("bean01", Bean01.class);
Bean02 bean02 = applicationContext.getBean("bean02", Bean02.class);
System.out.println(bean01);
System.out.println(bean02);
}
}
演算結果
Spring はデフォルトで循環依存関係の問題を解決しました (単一ケース Bean)
@Async アノテーションによって引き起こされる問題
@EnableAsync アノテーションを SpringConfig01 クラスに追加します
@Async アノテーションを使用して、Bean01 クラスの非同期メソッドを変更します
Bean01.java
package com.spring.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* @author honey
* @date 2023-08-23 17:50:53
*/
@Component
public class Bean01 {
@Autowired
private Bean02 bean02;
@Async
public void test() {
System.out.println(Thread.currentThread().getName() + "Bean01测试中...");
}
}
演算結果
"C:\Program Files\Java\jdk1.8.0_191\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2021.1.1\lib\idea_rt.jar=57456:C:\Program Files\JetBrains\IntelliJ IDEA 2021.1.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_191\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\rt.jar;E:\知识点资料(第四年)\Spring源码解读\代码\spring-circular-dependency\target\classes;D:\maven_jar\org\springframework\spring-core\5.2.1.RELEASE\spring-core-5.2.1.RELEASE.jar;D:\maven_jar\org\springframework\spring-jcl\5.2.1.RELEASE\spring-jcl-5.2.1.RELEASE.jar;D:\maven_jar\org\springframework\spring-beans\5.2.1.RELEASE\spring-beans-5.2.1.RELEASE.jar;D:\maven_jar\org\springframework\spring-context\5.2.1.RELEASE\spring-context-5.2.1.RELEASE.jar;D:\maven_jar\org\springframework\spring-aop\5.2.1.RELEASE\spring-aop-5.2.1.RELEASE.jar;D:\maven_jar\org\springframework\spring-expression\5.2.1.RELEASE\spring-expression-5.2.1.RELEASE.jar;D:\maven_jar\org\aspectj\aspectjrt\1.8.9\aspectjrt-1.8.9.jar;D:\maven_jar\org\apache\geronimo\bundles\aspectjweaver\1.6.8_2\aspectjweaver-1.6.8_2.jar" com.spring.test.SpringTest01
八月 23, 2023 7:07:05 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean01': Bean with name 'bean01' has been injected into other beans [bean02] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean01': Bean with name 'bean01' has been injected into other beans [bean02] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:624)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)
at com.spring.test.SpringTest01.main(SpringTest01.java:15)
Process finished with exit code 1
Spring が Bean をスキャンし、クラス メソッドが @Async によって変更されていることを検出すると、ポストプロセッサ AsyncAnnotationBeanPostProcessor を通じてプロキシ オブジェクトを生成します。ポストプロセッサの順序は AOP を処理するポストプロセッサよりも後であるため、 Spring が循環依存関係を処理できません。
@Lazy アノテーションを使用して、@Async アノテーションによって引き起こされる問題を解決する
Bean01 クラスの循環依存プロパティに @Lazy アノテーションを使用する
演算結果
Aop をオンにしてプロキシ オブジェクトを使用する例
プロジェクトの構造
プロジェクトコード
アスペクトAop.java
package com.spring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author honey
* @date 2023-08-23 18:26:33
*/
@Component
@Aspect
public class AspectAop {
@Pointcut("execution (* com.spring.bean.*.*(..))")
public void pointcut() {
}
@Around(value = "pointcut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
System.out.println("doAround advice start");
Object result = point.proceed();
System.out.println("doAround advice end");
return result;
}
}
次のような共通のメソッドを Bean01 と Bean02 に追加する必要があります。
package com.spring.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author honey
* @date 2023-08-23 17:52:55
*/
@Component
public class Bean02 {
@Autowired
private Bean01 bean01;
public void add() {
}
}
SpringConfig01.java
package com.spring.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @author honey
* @date 2023-08-23 17:59:37
*/
@Configuration
@ComponentScan(value = {
"com.spring.bean", "com.spring.aop"})
@EnableAspectJAutoProxy
public class SpringConfig01 {
}
演算結果
Spring が循環依存関係の問題をどのように解決するか
Spring の最下層は、3 レベルのキャッシュを通じて循環依存の問題を解決します。
レベル 1 キャッシュ: singletonObjects (シングルトン プールとも呼ばれます) は、完全なライフ サイクルを経た Bean オブジェクトを格納します。
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
第 2 レベルのキャッシュ: 初期に公開された Bean オブジェクトを格納する EarlySingletonObjects Bean のライフサイクルはまだ終了していません (プロパティはまだ完全には設定されていません)。
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
レベル 3 キャッシュ: singletonFactories、Bean を生成できるファクトリを格納します。
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
原理
2 つのオブジェクト A と B があり、A は B に依存し、B は A に依存するとします。
- A オブジェクトを作成する場合は B オブジェクトが必要となるため、A オブジェクトが 3 次キャッシュに格納されてから B オブジェクトが作成されます。
- B オブジェクトを作成するときに、A オブジェクトが必要であることがわかったので、1 次キャッシュを確認したところ、存在しないことがわかりました。次に、2 次キャッシュを確認したところ、まだ存在しないことがわかりました。次に、3 番目のキャッシュを確認しました。レベル キャッシュで A オブジェクトを見つけた後、その A オブジェクトを 3 番目のキャッシュに保存し、レベル 2 キャッシュで、レベル 3 キャッシュの A オブジェクトを削除しました。
- B オブジェクトの作成が完了し、B オブジェクトが一次キャッシュに格納されます(このとき、B オブジェクトが依存する A オブジェクトはまだ作成状態です)。作成が完了するまで A オブジェクトを作成し、その A オブジェクトを 1 次キャッシュに保存します (他のオブジェクトがオブジェクト A およびオブジェクト B に依存している場合、それらは 1 次キャッシュから直接取得できます)。
ソースコードの解釈
A オブジェクトを作成する場合、A オブジェクトがインスタンス化された後、A オブジェクトは ObjectFactory にカプセル化され、3 次キャッシュに格納されます。
AbstractBeanFactory.java
AbstractAutowireCapableBeanFactory.java
オブジェクトファクトリー.java
オブジェクトがシングルトン オブジェクトで、循環依存関係がオンになっていてオブジェクトが作成されている場合、オブジェクトは ObjectFactory オブジェクトにカプセル化され、3 次キャッシュに格納されます。このうち、ObjectFactory は関数型インターフェースであり、getObject() メソッドを呼び出すと、実際にはラムダステートメントが実行され、getEarlyBeanReference() メソッドが呼び出されます。
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
DefaultSingletonBeanRegistry.java
A オブジェクトのプロパティを設定するときに、A オブジェクトが B オブジェクトに依存していることが判明すると、B オブジェクトを取得しようとします。この時点では B オブジェクトが作成されていないため、B オブジェクトもそのプロパティに従います。 A オブジェクトと同じ作成ロジックですが、異なる B オブジェクトのプロパティを設定すると、B オブジェクトが A オブジェクトに依存しており、A オブジェクトは 3 次キャッシュから取得できることがわかります。
AbstractBeanFactory.java
DefaultSingletonBeanRegistry.java
このとき、不完全なオブジェクトは 2 次キャッシュに格納されるため、singletonFactory.getObject() メソッドの呼び出しは、実際には getEarlyBeanReference(beanName, mbd, bean) メソッドの呼び出しとなります。
正常であれば元のオブジェクトを返す
InstantiationAwareBeanPostProcessorAdapter.java
Aop が有効な場合は、Aop プロキシ オブジェクトを返します。
AbstractAutoProxyCreator.java
取得した A オブジェクトが B オブジェクトの属性として設定され、B オブジェクトのライフサイクル全体が実行された後、B オブジェクトは 1 次キャッシュに格納され、2 次キャッシュおよび 3 次キャッシュから削除されます。キャッシュ。ここでも ObjectFactory 関数インターフェイスが使用されます。
AbstractBeanFactory.java
DefaultSingletonBeanRegistry.java
B オブジェクトを取得した後、A オブジェクトのライフサイクルが実行され、実行完了後、B オブジェクトと同様に 1 次キャッシュに格納されます。
どのような状況で Spring は循環依存関係を解決できませんか?
- コンストラクター注入の循環依存性の問題。
- 非シングルトンオブジェクトの循環依存関係の問題。