Springの構造関数注入のサイクル依存問題

26680 ワード

一、循環依存
  • springのループ依存とは、主に2つのクラスが互いに@Autowiredによって自動的に依存して相手に注入されることを意味する.すなわち、クラスAは1つのクラスBのオブジェクト参照を含み、自動的に注入する必要があり、クラスBは1つのクラスAを含むオブジェクト参照も自動的に注入する必要がある.
  • サイクル依存問題に対してspringは注入方式によって異なる処理戦略を採用し、双方が属性値注入またはsetter法注入を使用している場合、springは自動的にサイクル依存注入問題を解決することができ、アプリケーションは成功に起動することができる.両方ともコンストラクション関数を使用して相手またはメインbeanオブジェクト(Spring起動中に先にロードされたbeanオブジェクト)にコンストラクション関数を使用して注入する場合、springはループ依存注入を解決できず、プログラムエラーで起動できません.

  • 二、解決できる循環依存
    サイクル依存の状況を解決できる
  • 主beanは、依存するbeanを属性またはsetter法によって注入し、構造関数によって注入しない.

  • サイクル依存解決の実現
    実装ベース
  • ループ依存性の解決:singletonObjects,earlySingletonObjects,singletonFactoriesの3段階キャッシュ実装.以下は、主にAbstractBeanFactoryのdoGetBeanメソッドで呼び出されるDefaultSingletonBeanRegistryのgetSingletonメソッドの実装です.
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //       singletonObject    
    	Object singletonObject = this.singletonObjects.get(beanName);
    	
    	//             ,
    	//          ,  singletonsCurrentlyInCreation   
    	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    		synchronized (this.singletonObjects) {
    		
    		    //       earlySingletonObjects    
    			singletonObject = this.earlySingletonObjects.get(beanName);
    			if (singletonObject == null && allowEarlyReference) {
    			
    			    //       singletonFactory      
    				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    				if (singletonFactory != null) {
    				
    				    //               
    					singletonObject = singletonFactory.getObject();
    					
    					//       earlySingletonObjects 
    					this.earlySingletonObjects.put(beanName, singletonObject);
    					
    				    //       
    					this.singletonFactories.remove(beanName);
    				}
    			}
    		}
    	}
    	return singletonObject;
    }
    

  • インプリメンテーションプロセス
  • マスターbeanオブジェクトの作成時にAbstractBeanFactoryがD e f a u l tSingletonBeanRegistryのgetSingletonメソッドを呼び出す:マスターbeanをsingletonsCurrentlyInCreationリストに入れることで、以上の3段階キャッシュ実装メソッドgetSingletonが、2、3段階の保存earlySingletonObjectsに入ることができ、singletonFactoriesがこのマスターbeanを検索し、主に依存beanにプライマリbeanを検索するときに使用されます.
    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        //       
        
    		//    beanName  singletonsCurrentlyInCreation ,          
    		beforeSingletonCreation(beanName);
    		
    	try {
    	    // getObject     AbstractAutowireCapableBeanFactory createBean  
    		singletonObject = singletonFactory.getObject();
    		newSingleton = true;
    	}
    			
    	//       
    		
    }
    
    protected void beforeSingletonCreation(String beanName) {
    	if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    		throw new BeanCurrentlyInCreationException(beanName);
    	}
    }
    
  • A b s t r a c t o w i r e CapableBeanFactoryのcreateBeanメソッドが呼び出され、createBeanメソッドはAbstractAutowireCapableBeanFactoryのdoCreateBeanメソッドが呼び出されます.doCreateBeanメソッドは、addSingletonFactoryメソッドを先に呼び出し、三次キャッシュsingletonFactoriesにマスターbeanを入れるタイプのObjectFactoryのsingletonFactoryオブジェクト作成ファクトリでは、依存するbeanオブジェクトを作成するときに、マスターbeanオブジェクト参照を三次キャッシュメカニズムで取得できます.
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    		throws BeanCreationException {
    		//       
    		
    		// 1.          bean  ,          ,    
    		instanceWrapper = createBeanInstance(beanName, mbd, args);
    
    		// 2.  singletonFactories   ,   bean  ,          
    		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    		 		
    		// 3. populateBean  :bean       
    		populateBean(beanName, mbd, instanceWrapper);
    
    		//       
    }
    
  • doCreateBeanメソッドでは、populateBeanを呼び出して属性付与を行い、ステップ2のaddSingletonFactoryメソッドはpopulateBeanメソッドを呼び出す前に呼び出されるので、populateBeanを呼び出してメインbeanオブジェクトに属性値注入またはsetter注入を行うと、メインbeanの作成ファクトリはsingletonFactoriesキャッシュに既に存在する.
  • populateBeanメソッドで依存するbeanオブジェクトを検索または作成します.依存するbeanオブジェクトを作成すると、AbstractBeanFactoryのdoGetBeanメソッドが呼び出されます.ループ依存性がある場合、依存beanは属性注入、setter法注入、または構造関数注入によって主beanを注入することができる.
  • 属性値またはsetterメソッドで注入された場合、ループ依存の問題はありません.プライマリbeanと依存するbeanは、コンストラクション関数を呼び出してオブジェクトを作成することに成功し、オブジェクト参照によって相手を参照することができます.属性値注入とsetterメソッド注入は、beanオブジェクトの後置プロセッサBeanPostProcessorによってそのbeanオブジェクトの属性値を注入する.
  • コンストラクション関数を介して依存beanオブジェクトがプライマリbeanオブジェクトに注入された場合、コンストラクション関数を呼び出して依存beanオブジェクトを作成し、プライマリbeanオブジェクトを検索する必要がある場合、検索時にAbstractBeanFactoryのdoGetBeanメソッドを呼び出してプライマリbeanオブジェクトを取得し続け、一方、AbstractBeanFactoryのdoGetBeanメソッド呼び出しgetSingletonメソッドは、3つのキャッシュがプライマリbeanオブジェクトを得ることができるかどうかをチェックします.
  • ステップ1およびステップ2から分かるように、getSingletonメソッドのsingletonsCurrentlyInCreationおよびsingletonFactoriesにはすでにマスターbeanに関する情報があるため、マスターbeanオブジェクトは、マスターbeanオブジェクトの作成工場singletonFactoriesによって事前に露出したマスターbeanオブジェクトを作成し、セカンダリーキャッシュearlySingletonObjectsを入れることによって検索され、したがって、この依存beanのプライマリbeanへの依存注入を解決することができ、依存オブジェクトbeanの作成に成功する.

  • この依存beanオブジェクトが作成されると、プライマリbeanのプロパティは完全に注入に成功する.一方、依存beanはプライマリbean内のオブジェクト参照を含むため、依存beanオブジェクトはプライマリbeanにアクセスして使用することができ、依存beanオブジェクトも完全なオブジェクトであり、最終的にプライマリbeanと依存beanとの間の循環依存問題を解決する.
  • 最後に、注入に成功すると、1つのbeanオブジェクトが1つのキャッシュsingletonObjectsに格納され、事前に露出したオブジェクトが格納されている2つのキャッシュearlySingletonObjectsとそのbeanオブジェクトの作成ファクトリキャッシュearlySingletonObjectsが除去されます.D e f a ultSingletonBeanRegistryのgetSingletonメソッド実装とaddSingletonメソッド実装は、次のようになります:
    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    	
            //       
        
    		//    beanName  singletonsCurrentlyInCreation ,          
    		beforeSingletonCreation(beanName);
    		
        	try {
        	    // getObject     AbstractAutowireCapableBeanFactory createBean  
    			singletonObject = singletonFactory.getObject();
    			newSingleton = true;
    		}
    			
    		//       
    		
    		if (newSingleton) {
    			//    bean   ,  singleObjects   
    			addSingleton(beanName, singletonObject);
    		}
    		
    		return singletonObject;
    	}
    }
    	
    protected void addSingleton(String beanName, Object singletonObject) {
    	synchronized (this.singletonObjects) {
    	    //       
    		this.singletonObjects.put(beanName, singletonObject);
    		//        
    		this.singletonFactories.remove(beanName);
    		this.earlySingletonObjects.remove(beanName);
    		this.registeredSingletons.add(beanName);
    	}
    }
    
  • 三、解決できない循環依存問題
  • 主beanに依存するbeanをコンストラクション関数によって注入する.
  • 以下controllerを主beanとし、サービスは依存するbean:
    @RestController
    public class AccountController {
        private static final Logger LOG = LoggerFactory.getLogger(AccountController.class);
    
        private AccountService accountService;
    
        //         
        //        required true,          
        @Autowire
        // @Autowired(required = false)
        public AccountController(AccountService accountService) {
            this.accountService = accountService;
        }
        
    }
    
    @Service
    public class AccountService {
        private static final Logger LOG = LoggerFactory.getLogger(AccountService.class);
        
        //        
        @Autowired
        private AccountController accountController;
        
    }
    
    起動印刷は以下の通りである:
    
    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    The dependencies of some of the beans in the application context form a cycle:
    
    ┌─────┐
    |  accountController defined in file [/Users/xieyizun/study/personal-projects/easy-web/target/classes/com/yzxie/easy/log/web/controller/AccountController.class]
    ↑     ↓
    |  accountService (field private com.yzxie.easy.log.web.controller.AccountController com.yzxie.easy.log.web.service.AccountService.accountController)
    └─────┘
    
  • 依存するbeanがプライマリbeanに属性値またはsetter法によって注入され、依存するbeanがコンストラクション関数を使用してプライマリbeanオブジェクトに注入される場合、ループ依存の問題は発生しません.
    @RestController
    public class AccountController {
        private static final Logger LOG = LoggerFactory.getLogger(AccountController.class);
    
        //      
        @Autowired
        private AccountService accountService;
        
    }
    
    @Service
    public class AccountService {
        private AccountController accountController;
    
        //       
        @Autowired
        public AccountService(AccountController accountController) {
            this.accountController = accountController;
        }
        
    }
    
    

  • 四、まとめ
  • ループ依存が存在する場合、主beanオブジェクトは構造関数によって依存するbeanオブジェクトを注入することができず、依存するbeanオブジェクトは制限されず、すなわち、3つの注入方式のいずれかによって主beanオブジェクトを注入することができる.
  • 主beanオブジェクトがコンストラクション関数で依存するbeanオブジェクトを注入すると、依存するbeanオブジェクトがどのように主beanを注入しても、ループ依存の問題を解決することができず、プログラムが起動できません.
  • 主な理由は、主beanオブジェクトがコンストラクション関数を介して依存beanオブジェクトに注入された場合、依存beanオブジェクトを作成することができず、依存beanオブジェクトの参照を取得するためである.下記のコードで示しているからです.
  • メインbeanオブジェクトを作成し、呼び出し順序:
  • コンストラクタを呼び出す.3レベルのキャッシュを配置します.コンストラクション関数を呼び出すと、依存するbeanオブジェクトの作成がトリガーされます.
    	    // bean             
    	    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    	    		throws BeanCreationException {
    	    		//       
    	    	
    	    		// 1.          bean  ,          ,    
    	    		instanceWrapper = createBeanInstance(beanName, mbd, args);
    	    	
    	    
    	    		// 2.  singletonFactories   ,   bean  ,          
    	    		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    	    	
    	    
    	    		// 3. populateBean  :bean       
    	    		populateBean(beanName, mbd, instanceWrapper);
    	    
    	    		
    	    		//       
    	    	return exposedObject;
    	    }
    
  • createBeanInstanceは、コンストラクション関数を呼び出してプライマリbeanオブジェクトを作成し、コンストラクション関数に依存するbeanが注入されますが、このときaddSingletonFactoryメソッドは実行されず、プライマリbeanオブジェクトの作成ファクトリを3レベルキャッシュsingletonFactoriesに追加します.
  • したがってcreateBeanInstance内部で、プライマリbeanオブジェクトを注入および作成する際に、コンストラクション関数に他のbeanオブジェクトへの依存が存在し、プライマリbeanオブジェクトへの依存も存在する場合、ループ依存の問題が発生します.原理は次のとおりです.
    メインbeanオブジェクトはA、AオブジェクトはBオブジェクトに依存し、BオブジェクトもAオブジェクトに依存している.Aオブジェクトを作成すると、Bオブジェクトの作成がトリガーされ、BはメインbeanオブジェクトAの参照を3段階キャッシュメカニズムで取得できない(すなわち、Bがコンストラクション関数でAを注入した場合、Bオブジェクトを作成できません.属性注入またはsetterメソッドでAを注入した場合、Bオブジェクトを作成した後、Bオブジェクトに属性付与を行うと、populateBeanメソッドにも戻りません)を選択します.したがって、プライマリbeanオブジェクトに依存するBを作成できません.プライマリbeanオブジェクトAを作成すると、createBeanInstanceメソッドが返されず、コードデッドロックが発生し、プログラムレポートのループ依存エラーが発生します.