Spring循環依存の解決方法は本当に分かりましたか?
紹介する
まず何が循環依存かを話します。悪い依存性は循環参照で、二つ以上のbeanは相互参照で、最終的には一つのリングを形成します。Springは、Aを初期化する時はBを注入し、Bを初期化する時はAを注入し、Springを起動した後はこの2つのBeanを初期化します。
Springのサイクル依存は二つのシーンがあります。
属性の循環依存性は主に3 mapで解決される。
コンストラクタの循環依存性
@Component
public class ConstructorA {
private ConstructorB constructorB;
@Autowired
public ConstructorA(ConstructorB constructorB) {
this.constructorB = constructorB;
}
}
@Component
public class ConstructorB {
private ConstructorA constructorA;
@Autowired
public ConstructorB(ConstructorA constructorA) {
this.constructorA = constructorA;
}
}
@Configuration
@ComponentScan("com.javashitang.dependency.constructor")
public class ConstructorConfig {
}
public class ConstructorMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(ConstructorConfig.class);
System.out.println(context.getBean(ConstructorA.class));
System.out.println(context.getBean(ConstructorB.class));
}
}
Constructor Mainのmainメソッドを実行する時、最初の行で異常を報告します。Springは全てのBeanを初期化できないということです。つまり上のような形の循環依存Springは解決できないということです。コンストラクターやコンストラクタBコンストラクタのパラメータに@Lazy注を加えると解決できます。
@Autowired
public ConstructorB(@Lazy ConstructorA constructorA) {
this.constructorA = constructorA;
}
私たちは主に属性の循環依存性に関心を持っていますので、コンストラクタの循環依存性はあまり分析しません。属性の循環依存性
まず、属性の循環依存性を示します。
@Component
public class FieldA {
@Autowired
private FieldB fieldB;
}
@Component
public class FieldB {
@Autowired
private FieldA fieldA;
}
@Configuration
@ComponentScan("com.javashitang.dependency.field")
public class FieldConfig {
}
public class FieldMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(FieldConfig.class);
// com.javashitang.dependency.field.FieldA@3aa9e816
System.out.println(context.getBean(FieldA.class));
// com.javashitang.dependency.field.FieldB@17d99928
System.out.println(context.getBean(FieldB.class));
}
}
Spring容器は正常に起動し、FieldAとFieldBの2つのBeanが得られます。属性の循環は面接でよく聞かれます。全体的には複雑ではないですが、Spring Beanの初期化過程に関わるので、複雑な感じがします。デモを書いて全体の過程を見せます。
SpringのBeanの初期化過程は実は複雑です。Demoを理解しやすくするために、Spring Beanの初期化過程を2つの部分に分けます。
以下はDemoを書き始めます。Object FactoryインターフェースはBeanを生産するために使われます。Springで定義されているインターフェースと同じです。
public interface ObjectFactory<T> {
T getObject();
}
public class DependencyDemo {
// Bean
private final Map<String, Object> singletonObjects =
new ConcurrentHashMap<>(256);
// Bean ,
private final Map<String, ObjectFactory<?>> singletonFactories =
new HashMap<>(16);
// Bean,
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
public <T> T getBean(Class<T> beanClass) throws Exception {
// Bean
String beanName = beanClass.getSimpleName();
// ,
Object initObj = getSingleton(beanName, true);
if (initObj != null) {
return (T) initObj;
}
// bean
singletonsCurrentlyInCreation.add(beanName);
// bean
Object object = beanClass.getDeclaredConstructor().newInstance();
singletonFactories.put(beanName, () -> {
return object;
});
// bean,
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
// class
Class<?> fieldClass = field.getType();
field.set(object, getBean(fieldClass));
}
//
singletonObjects.put(beanName, object);
singletonsCurrentlyInCreation.remove(beanName);
return (T) object;
}
/**
* allowEarlyReference Spring , true
* allowEarlyReference false , ,
*/
public Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null
&& isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory =
this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
}
}
}
}
return singletonObject;
}
/**
* bean
*/
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
}
テスト
public static void main(String[] args) throws Exception {
DependencyDemo dependencyDemo = new DependencyDemo();
//
Class[] classes = {A.class, B.class};
// bean
for (Class aClass : classes) {
dependencyDemo.getBean(aClass);
}
// true
System.out.println(
dependencyDemo.getBean(B.class).getA() == dependencyDemo.getBean(A.class));
// true
System.out.println(
dependencyDemo.getBean(A.class).getB() == dependencyDemo.getBean(B.class));
}
簡単ですか?私たちは2つのmapだけでSpringのサイクル依存を解決しました。2つのMapで循環依存ができますが、なぜSpringは3つのMapを使いますか?
理由は簡単です。私たちはシングルトンFactoresからビーンNameによって相応のObject Factoryを取得して、getObject()を呼び出して対応するビーンに戻ります。私たちの例では
Object Factoryの実現は簡単です。実例化されたオブジェクトを直接返しますが、Springではこれほど簡単ではありません。実行過程は複雑です。Object Factoryを手に入れるたびにgetObjectを呼び出して、Object Factoryで作成したオブジェクトをそのままキャッシュしておくと効率がアップします。
例えばAはBとCに依存し、BとCはAに依存し、キャッシュをしないと初期化BとCはAに対応するObject FactoryのgetObject()メソッドを呼び出す。キャッシュをするならBまたはCで一回だけ呼び出すだけでいいです。
考えが分かりました。上のコードを波に変えてキャッシュを入れます。
public class DependencyDemo {
// Bean
private final Map<String, Object> singletonObjects =
new ConcurrentHashMap<>(256);
// Bean ,
private final Map<String, ObjectFactory<?>> singletonFactories =
new HashMap<>(16);
// Bean Bean
private final Map<String, Object> earlySingletonObjects =
new HashMap<>(16);
// Bean,
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
public <T> T getBean(Class<T> beanClass) throws Exception {
// Bean
String beanName = beanClass.getSimpleName();
// ,
Object initObj = getSingleton(beanName, true);
if (initObj != null) {
return (T) initObj;
}
// bean
singletonsCurrentlyInCreation.add(beanName);
// bean
Object object = beanClass.getDeclaredConstructor().newInstance();
singletonFactories.put(beanName, () -> {
return object;
});
// bean,
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
// class
Class<?> fieldClass = field.getType();
field.set(object, getBean(fieldClass));
}
singletonObjects.put(beanName, object);
singletonsCurrentlyInCreation.remove(beanName);
earlySingletonObjects.remove(beanName);
return (T) object;
}
/**
* allowEarlyReference Spring , true
*/
public Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null
&& isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory =
this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
}
私たちが書いたgetSingletonの実現はorg.springframe ebork.beans.factory.support.DefafaultSingleton BenRegistryと全く同じで、この方法はほとんどすべてSpring循環依存を分析する文章で、今回の仕事の原理が分かります。一難をまとめる
ブログを参照してください
[1]https://mp.weixin.qq.com/s/gBr3UfC1HRcw4U-ZMmtRaQ
[2]https://mp.weixin.qq.com/s/5mwkgJB7GyLdKDgzijyvXw
比較的詳しい
[1]https://zhuanlan.zhihu.com/p/84267654
[2]https://juejin.im/post/5c98a7b4f265da60ee12e9b2
ここで、Springの循環依存の解決方法について、本当に分かりましたか?この記事を紹介します。もっと関連のあるSpring循環依存の内容は以前の文章を検索したり、次の関連記事を見たりしてください。これからもよろしくお願いします。