1.フロンティア
SpringのBean作成の記事では、Bean作成の複雑なプロセスについて学びました。その記事では、循環依存に関するいくつかの問題についても説明しました。循環依存は無限ループであり、解決できない問題です。この記事では、デモの例を具体的に示します。 Springがこの解決できない問題をどのように解決するかを分析します
第二に、循環依存の定義
循環依存関係は循環参照です。つまり、2つ以上のBeanが相互に保持します。たとえば、AはBを参照し、BはCを参照し、CはAを参照し、それらは相互に参照してリングを形成します。これはループ呼び出しとは区別されます。ループ呼び出しはメソッド間のループ呼び出しです。ループ呼び出しは解決できません。最終条件がない限り、それは無限ループです。ループ呼び出しの結果は、最終的にメモリオーバーフローにつながります。
3、Springは循環依存を解決します
Springコンテナーには循環依存コンストラクター依存関係があり、セッター循環依存関係は2つです。以下に例として説明します。
3.1コンストラクターの依存関係(Springはそれを解決できません)
コンストラクターの注入によって形成された循環依存関係を示します。この依存関係はSpringでは解決できず、循環依存関係を示すためにBeanCurrentlyInCreationExceptionをスローすることしかできません。その理由は、SpringがBeanインスタンスBeanWrapperを作成するときに、作成されるBeanがまったくキャッシュされないためです。
3.1.1デモの例
エンティティクラスコードは次のとおりです。
import org.springframework.beans.factory.InitializingBean;
/**
* A测试
*
* @date 2019-12-30 11:18
**/
public class BeanATest implements InitializingBean {
private BeanBTest b;
public BeanATest() {
System.out.println("BeanATest 创建开始");
}
public BeanATest(BeanBTest b) {
System.out.println("BeanATest 创建开始,依赖 BeanBTest 传入");
this.b = b;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("BeanATest 创建结束,依赖 BeanBTest " + b);
}
}
import org.springframework.beans.factory.InitializingBean;
/**
* B测试
*
* @date 2019-12-30 11:18
**/
public class BeanBTest implements InitializingBean {
private BeanCTest c;
public BeanBTest() {
System.out.println("BeanBTest 创建开始");
}
public BeanBTest(BeanCTest c) {
System.out.println("BeanBTest 创建开始,依赖 BeanCTest传入");
this.c = c;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("BeanBTest 创建结束,依赖 BeanCTest " + c);
}
}
import org.springframework.beans.factory.InitializingBean;
/**
* C测试
*
* @date 2019-12-30 11:18
**/
public class BeanCTest implements InitializingBean {
private BeanATest a;
public BeanCTest() {
System.out.println("BeanCTest 创建开始");
}
public BeanCTest(BeanATest a) {
System.out.println("BeanCTest 创建开始,依赖 BeanATest 传入");
this.a = a;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("BeanCTest 创建结束,依赖 BeanATest " + a);
}
}
resourcesディレクトリの下にある構成ファイルapplication_circle.xmlの内容は次のとおりです。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<!-- BeanATest -->
<bean id="beanATest" class="com.springboot.demo.bean.circle.BeanATest">
<constructor-arg index="0" ref="beanBTest"/>
</bean>
<!-- BeanBTest -->
<bean id="beanBTest" class="com.springboot.demo.bean.circle.BeanBTest">
<constructor-arg index="0" ref="beanCTest"/>
</bean>
<!-- BeanCTest -->
<bean id="beanCTest" class="com.springboot.demo.bean.circle.BeanCTest">
<constructor-arg index="0" ref="beanATest"/>
</bean>
</beans>
テストコードは次のとおりです。
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* ApplicationContext测试
*
* @date 2019-12-24 11:31
**/
public class ApplicationContextTest {
public static void main(String[] args) {
System.out.println("开始初始化ApplicationContext容器");
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application_circle.xml");
System.out.println("ApplicationContext容器初始化完毕");
applicationContext.close();
}
}
操作の結果は次のとおりです。
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanATest': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:367)
... 37 more
結論:Springは、コンストラクターの循環依存に直接例外をスローする問題を解決できません。
3.2セッター循環依存(Springはそれを解決できます)
セッターメソッドインジェクションメソッドによって形成される循環依存関係を表します。セッターインジェクションによる依存関係は、コンストラクターインジェクションを完了したばかりで、Springコンテナーを介して他のステップ(プロパティの入力など)を事前に完了していないBeanを公開することで完了します。 、およびシングルトンスコープのBean循環依存関係のみを解決できます。ファクトリメソッドObjectFactoryを事前に公開して、他のBeanがBeanを参照できるようにすることで、ソースコードは次のようになります。
// DefaultSingletonBeanRegistry 的 addSingletonFactory 方法
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
// beanName 对应的 bean 没有被缓存
if (!this.singletonObjects.containsKey(beanName)) {
// 增加 beanName 对应的 ObjectFactory 缓存
this.singletonFactories.put(beanName, singletonFactory);
// 移除 beanName 对应的早期缓存
this.earlySingletonObjects.remove(beanName);
// registeredSingletons 也增加 beanName 对应的缓存
this.registeredSingletons.add(beanName);
}
}
}
3.2.1デモの例
エンティティクラスコードは次のとおりです。
import org.springframework.beans.factory.InitializingBean;
/**
* A测试
*
* @date 2019-12-30 11:18
**/
public class BeanATest implements InitializingBean {
private BeanBTest b;
public BeanATest() {
System.out.println("BeanATest 创建开始");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("BeanATest 创建结束,依赖 BeanBTest " + b);
}
public BeanBTest getB() {
return b;
}
public void setB(BeanBTest b) {
this.b = b;
}
}
import org.springframework.beans.factory.InitializingBean;
/**
* B测试
*
* @date 2019-12-30 11:18
**/
public class BeanBTest implements InitializingBean {
private BeanCTest c;
public BeanBTest() {
System.out.println("BeanBTest 创建开始");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("BeanBTest 创建结束,依赖 BeanCTest " + c);
}
public BeanCTest getC() {
return c;
}
public void setC(BeanCTest c) {
this.c = c;
}
}
import org.springframework.beans.factory.InitializingBean;
/**
* C测试
*
* @date 2019-12-30 11:18
**/
public class BeanCTest implements InitializingBean {
private BeanATest a;
public BeanCTest() {
System.out.println("BeanCTest 创建开始");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("BeanCTest 创建结束,依赖 BeanATest " + a);
}
public BeanATest getA() {
return a;
}
public void setA(BeanATest a) {
this.a = a;
}
}
resourcesディレクトリの下にある構成ファイルapplication_circle.xmlの内容は次のとおりです。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<!-- BeanATest -->
<bean id="beanATest" class="com.springboot.demo.bean.circle.BeanATest">
<property name="b" ref="beanBTest"/>
</bean>
<!-- BeanBTest -->
<bean id="beanBTest" class="com.springboot.demo.bean.circle.BeanBTest">
<property name="c" ref="beanCTest"/>
</bean>
<!-- BeanCTest -->
<bean id="beanCTest" class="com.springboot.demo.bean.circle.BeanCTest">
<property name="a" ref="beanATest"/>
</bean>
</beans>
テストコードは変更されておらず、結果は次のとおりです。
开始初始化ApplicationContext容器
BeanATest 创建开始
BeanBTest 创建开始
BeanCTest 创建开始
BeanCTest 创建结束,依赖 BeanATest com.springboot.demo.bean.circle.BeanATest@a1153bc
BeanBTest 创建结束,依赖 BeanCTest com.springboot.demo.bean.circle.BeanCTest@55141def
BeanATest 创建结束,依赖 BeanBTest com.springboot.demo.bean.circle.BeanBTest@55182842
ApplicationContext容器初始化完毕
Beanを作成する手順は次のとおりです。
1)SpringコンテナはシングルトンBeanATestを作成します。まず、パラメータなしのコンストラクタに基づいてBeanATestを作成し、ObjectFactoryを公開して事前に作成されたBeanを返し、beanATestとObjectFactoryの対応をsingletonFactoriesオブジェクトにキャッシュしてから実行します。 setBメソッドインジェクションBeanBTest
2)SpringコンテナはシングルトンBeanBTestを作成します。最初に、パラメータなしのコンストラクタに基づいてBeanBTestを作成し、ObjectFactoryを公開して、事前に作成されたBeanを返し、beanBTestとObjectFactoryの間の対応をsingletonFactoriesオブジェクトにキャッシュしてから実行します。 setCメソッドインジェクションBeanCTest
3)SpringコンテナはシングルトンBeanCTestを作成します。まず、パラメータなしのコンストラクタに基づいてBeanCTestを作成し、ObjectFactoryを公開して事前に作成されたBeanを返し、beanCTestとObjectFactoryの対応をsingletonFactoriesオブジェクトにキャッシュしてから実行します。 setAメソッドはBeanATestに注入されます。BeanATestを注入すると、オブジェクトファクトリObjectFactoryが事前に公開され(BeanATestとObjectFactory間のマッピング関係はsingletonFactoriesキャッシュにキャッシュされます)、ObjectFactoryのgetObjectメソッドを呼び出すと作成されたBeanATestが直接返されます。
4)BeanCTestが作成され、依存関係がBeanCTestに注入された後、BeanBTestが作成され、最後に依存関係がBeanBTestに注入された後、BeanATestが作成されます。
結論:SpringコンテナはシングルトンBeanのセッター循環依存問題を解決できます
シングルトンBeanの場合、ApplicationContextのsetAllowCircularReferencesメソッドを設定することにより、循環依存を許可するかどうかを決定できます。
3.3プロトタイプBeanの循環依存関係(Springはそれを解決できません)
PrototypeスコープのBeanの場合、SpringコンテナーはPrototypeスコープのBeanをキャッシュしないため、Springコンテナーは依存性注入を完了できません。そのため、事前に作成されているBeanを公開できません。
Springはコンストラクターの循環依存の問題を解決できないため、PrototypeスコープのBeanのセッターの循環依存を示し、SpringがPrototypeBeanの循環依存を解決できるかどうかを確認する例のみが必要です。
3.3.1デモの例
エンティティクラスコード3.2で一貫性がある
resourcesディレクトリの設定ファイルapplication_circle.xmlは問題なく、内容は次のとおりです。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<!-- BeanATest -->
<bean id="beanATest" class="com.springboot.demo.bean.circle.BeanATest" scope="prototype">
<property name="b" ref="beanBTest"/>
</bean>
<!-- BeanBTest -->
<bean id="beanBTest" class="com.springboot.demo.bean.circle.BeanBTest" scope="prototype">
<property name="c" ref="beanCTest"/>
</bean>
<!-- BeanCTest -->
<bean id="beanCTest" class="com.springboot.demo.bean.circle.BeanCTest" scope="prototype">
<property name="a" ref="beanATest"/>
</bean>
</beans>
テストコードは次のとおりです。
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* ApplicationContext测试
*
* @date 2019-12-24 11:31
**/
public class ApplicationContextTest {
public static void main(String[] args) {
System.out.println("开始初始化ApplicationContext容器");
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application_circle.xml");
System.out.println("ApplicationContext容器初始化完毕");
// ApplicationContext 启动时只会创建非延迟加载的 Singleton Bean,而 Prototype Bean 是延迟加载,需要手动调用
applicationContext.getBean("beanATest");
applicationContext.close();
}
}
操作の結果は次のとおりです。
結論:SpringコンテナはプロトタイプBeanの循環依存問題を解決できません
3.4レベル3キャッシュの必要性
AはBを指し、BはAを指します
3.4.1 3レベルのキャッシュを削除した後、ABオブジェクトを作成する手順は次のとおりです。
1)Aがインスタンス化されたら、A @ 1952を作成し、第2レベルのキャッシュA @ 1952を追加します。
2)A属性が入力されると、BオブジェクトB @ 2449が作成され、第2レベルのキャッシュB @ 2449が追加されます。
3)属性Bが入力されると、A @ 1952が第2レベルのキャッシュで取得され、bの属性aはこの時点ですでに半製品です。
4)メソッドBが初期化されると、cglib B @ 3349が生成され、エージェントBにaの属性値がありません。
5)cglib B @ 3349は第1レベルのキャッシュに配置され、B @ 2449は第2レベルのキャッシュから削除されます
6)A属性が入力されると、プロキシcglib B @ 3349が返されます。このとき、A @ 1952のb属性はcglibB @ 3349です。
7)Aメソッドが初期化されると、cglib A @ 3849オブジェクトが生成され、プロキシAにb属性値がなく、exposedObject!= Beanオブジェクトが作成されます。
8)Aの依存オブジェクトB(cglib A @ 3849)がまだ作成されていないことを確認し(この時点で、Aが依存するすべての属性が作成されているはずです)、この時点で例外がスローされます。
結論:3レベルのキャッシュを削除した後、A属性が入力された後に生成されたA @ 1952オブジェクトと、Aメソッドが初期化された後に生成されたプロキシオブジェクトcglib A @ 3849に一貫性がありません。循環依存チェックを実行すると、 AのB属性がインスタンス化されていないことが判明したため、例外がスローされました
3.4.2 3レベルのキャッシュを保持した後にABオブジェクトを作成する手順は、次のとおりです。
1)Aがインスタンス化されると、A @ 1917が作成され、3レベルのキャッシュラムダ式が追加されます。
2)A属性が入力されたら、BオブジェクトB @ 2260を作成し、3レベルのキャッシュラムダ式を追加します
3)B属性がいっぱいになると、第3レベルのキャッシュを使用してgetObjectメソッドを呼び出してプロキシcglib A @ 2684を取得し、cglib A @ 2684を第2レベルのキャッシュに追加し、第3レベルのキャッシュでAを削除します。このとき、bの属性はプロキシオブジェクトであり、すでに半製品になっています。
4)メソッドBが初期化されると、cglib B @ 2915が生成され、エージェントBにaの属性値がありません。
5)Bオブジェクトcglib B @ 2915が第1レベルのキャッシュに入れられ、第3レベルのキャッシュが削除されますb
6)A属性が入力されると、プロキシcglib B @ 2915が返されます。このとき、A @ 1917のb属性はcglibB @ 2915です。
7)Aメソッドが初期化されたとき、earlyProxyReferencesのオブジェクトはまだA @ 1917オブジェクトであるため、プロキシオブジェクトAは生成されません。この時点でexposedObject == Bean、プロキシオブジェクトcglib A @ 2684がexposedObjectに割り当てられ、そこで循環依存関係をチェックする必要はありません。
結論:3レベルのキャッシュが保持された後、A属性が入力された後に生成されたA @ 1917オブジェクトは、Aメソッドが初期化された後のオブジェクトAと一致するため、循環依存チェックを行う必要はありません。
概要:第3レベルのキャッシュは、主に、依存オブジェクトがプロキシオブジェクトを作成する必要がある場合の問題を解決するためのものです。依存オブジェクトがプロキシオブジェクトを作成する必要がない場合は、第1レベルと第2レベルのキャッシュを使用します。