Spring循環依存の解決方法は本当に分かりましたか?



紹介する
まず何が循環依存かを話します。悪い依存性は循環参照で、二つ以上のbeanは相互参照で、最終的には一つのリングを形成します。Springは、Aを初期化する時はBを注入し、Bを初期化する時はAを注入し、Springを起動した後はこの2つのBeanを初期化します。

Springのサイクル依存は二つのシーンがあります。
  • コンストラクタの循環依存性
  • 属性の循環依存性
  • コンストラクタの循環依存性は,コンストラクタに@Lazy注解遅延負荷を使用することができる。注入依存時には、まずプロキシオブジェクトを注入し、最初に使用したときに、オブジェクトを作成して注入を完了します。
    属性の循環依存性は主に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つの部分に分けます。
  • beanの実装プロセス、すなわち、構成関数を呼び出してオブジェクトを作成する
  • beanの初期化プロセス、すなわち、beanを充填する各種属性
  • bean初期化プロセスが完了すれば、beanは正常に作成されます。
    以下は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循環依存を分析する文章で、今回の仕事の原理が分かります。
    一難をまとめる
  • beanを取る時はまずsingleton Objects(一級キャッシュ)から
  • を取得します。
  • が取得できず、オブジェクトが作成中である場合は、earlySingleton Object(二段キャッシュ)から
  • を取得する。
  • もしまだ取れないなら、singleton Factores(三段キャッシュ)から取得し、得られたオブジェクトをearlySingleton Object(二段キャッシュ)に入れ、beanに対応するsingleton Factores(三段キャッシュ)を
  • に消去する。
  • bean初期化が完了し、singleton Object(一級キャッシュ)に入れて、beanに対応するearlySingleton Object(二級キャッシュ)を
  • をクリアします。
    ブログを参照してください
    [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循環依存の内容は以前の文章を検索したり、次の関連記事を見たりしてください。これからもよろしくお願いします。