Spring IOC原理補充說明(迴圈依賴、Bean作用域等)
前言
通過之前的幾篇文章將Spring基於XML配置的IOC原理分析完成,但其中還有一些比較重要的細節沒有分析總結,比如迴圈依賴的解決、作用域的實現原理、BeanPostProcessor的執行時機以及SpringBoot零配置實現原理(@ComponentScan、@Import、@ImportSource、@Bean註解的使用和解析)等等。下面就先來看看迴圈依賴是怎麼解決的,在此之前一定要熟悉整個Bean的例項化過程,本篇只會貼出關鍵性程式碼。
正文
迴圈依賴
首先來看幾個問題:
什麼是迴圈依賴?
在熟悉了Bean例項化原理後,你會怎麼解決迴圈依賴的問題?
Spring怎麼解決迴圈依賴?有哪些迴圈依賴可以被解決?哪些又不能?
什麼是迴圈依賴?
這個概念很容易理解,簡單說就是兩個類相互依賴,類似執行緒死鎖的問題,也就是當建立A物件時需要注入B的依賴物件,但B同時也依賴A,那到底該先建立A還是先建立B呢?
Spring是如何解決迴圈依賴的?
探究Spring的解決方法之前,我們首先得搞清楚Spring Bean有幾種依賴注入的方式:
通過建構函式
通過屬性
通過方法(不一定是setter方法,只要在方法上加上了@Autowired,都會進行依賴注入)
其次,Spring作用域有singleton、prototype、request、session等等,但在非單例模式下發生迴圈依賴是會直接丟擲異常的,下面這個程式碼不知道你還有沒有印象,在AbstractBeanFactory.doGetBean中有這個判斷:
if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); }
為什麼這麼設計呢?反過來想,如果不這麼設計,你怎麼知道迴圈依賴到底是依賴的哪個物件呢?搞清楚了這個再來看哪些依賴注入的方式發生迴圈依賴是可以解決,而那些又不能。結論是建構函式方式沒辦法解決迴圈依賴,其它兩種都可以。
我們先來看看為什麼通過屬性注入和方法注入可以解決。回憶一下Bean的例項化過程:
protected Object doCreateBean(final String beanName,final RootBeanDefinition mbd,final @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { //建立例項 instanceWrapper = createBeanInstance(beanName,mbd,args); } final Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { // Bean例項化完成後收集類中的註解(@PostConstruct,@PreDestroy,@Resource, @Autowired,@Value) applyMergedBeanDefinitionPostProcessors(mbd,beanType,beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(),beanName,"Post-processing of merged bean definition failed",ex); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. // 單例bean提前暴露 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"); } //這裡著重理解,對理解迴圈依賴幫助非常大,重要程度 5 新增三級快取 addSingletonFactory(beanName,() -> getEarlyBeanReference(beanName,bean)); } // Initialize the bean instance. Object exposedObject = bean; try { //ioc di,依賴注入的核心方法,該方法必須看 populateBean(beanName,instanceWrapper); //bean 例項化+ioc依賴注入完以後的呼叫,非常重要 exposedObject = initializeBean(beanName,exposedObject,mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(),"Initialization of bean failed",ex); } } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName,false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName,"Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] 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."); } } } } // Register bean as disposable. try { //註冊bean銷燬時的類DisposableBeanAdapter registerDisposableBeanIfNecessary(beanName,bean,mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(),"Invalid destruction signature",ex); } return exposedObject; }
仔細看這個過程其實不難理解,首先Spring會通過無參構造例項化一個空的A物件,例項化完成後會呼叫addSingletonFactory存入到三級快取中(注意這裡存入的是singletonFactory物件):
protected void addSingletonFactory(String beanName,ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory,"Singleton factory must not be null"); synchronized (this.singletonObjects) { // 一級快取 if (!this.singletonObjects.containsKey(beanName)) { System.out.println("========set value to 3 level cache->beanName->" + beanName + "->value->" + singletonFactory); // 三級快取 this.singletonFactories.put(beanName,singletonFactory); // 二級快取 this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
然後才會去依賴注入觸發類B的例項化,所以這時快取中已經存在了一個空的A物件;同樣B也是通過無參構造例項化,B依賴注入又呼叫getBean獲取A的例項,而在建立物件之前,先是從快取中獲取物件:
//從快取中拿例項 Object sharedInstance = getSingleton(beanName); protected Object getSingleton(String beanName,boolean allowEarlyReference) { //根據beanName從快取中拿例項 //先從一級快取拿 Object singletonObject = this.singletonObjects.get(beanName); //如果bean還正在建立,還沒建立完成,其實就是堆記憶體有了,屬性還沒有DI依賴注入 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //從二級快取中拿 singletonObject = this.earlySingletonObjects.get(beanName); //如果還拿不到,並且允許bean提前暴露 if (singletonObject == null && allowEarlyReference) { //從三級快取中拿到物件工廠 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //從工廠中拿到物件 singletonObject = singletonFactory.getObject(); //升級到二級快取 System.out.println("======get instance from 3 level cache->beanName->" + beanName + "->value->" + singletonObject ); this.earlySingletonObjects.put(beanName,singletonObject); //刪除三級快取 this.singletonFactories.remove(beanName); } } } } return singletonObject; }
很明顯,會從三級快取中拿到singletonFactory物件並呼叫getObject方法,這是一個Lambda表示式,在表示式中又呼叫了getEarlyBeanReference方法:
protected Object getEarlyBeanReference(String beanName,RootBeanDefinition mbd,Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject,beanName); } } } return exposedObject; }
這裡你點進去看會發現都是返回之前我們建立的空的A物件,因此B物件能夠依賴注入完成並存入到一級快取中,接著A物件繼續未完成的依賴注入自然是可以成功的,也存入到一級快取中。Spring就是這樣通過快取解決了迴圈依賴,但是不知道你注意到沒有在上面的getSingleton方法中,從三級快取中拿到物件後,會新增到二級快取並刪除三級快取,這是為什麼呢?這個二級快取有什麼用呢?
其實也很簡單,就是為了提高效率的,因為在getEarlyBeanReference方法中是迴圈呼叫BeanPostProcessor類的方法的,當只有一對一的依賴時沒有什麼問題,但是當A和B相互依賴,A又和C相互依賴,A在注入完B觸發C的依賴注入時,這個迴圈還有必要麼?讀者們可以自行推演一下整個過程。
至此,Spring是如何解決迴圈依賴的相信你也很清楚了,現在再來看通過建構函式依賴注入為什麼不能解決迴圈依賴是不是也很清晰了?因為通過建構函式例項化並依賴注入是沒辦法快取一個例項物件供依賴物件注入的。
作用域實現原理以及如何自定義作用域
作用域實現原理
在Spring中主要有reqest、session、singleton、prototype等等幾種作用域,前面我們分析了singleton建立bean的原理,是通過快取來實現的,那麼其它的呢?還是回到AbstractBeanFactory.doGetBean方法中來:
if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName,() -> { try { return createBean(beanName,args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process,to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } }); // 該方法是FactoryBean介面的呼叫入口 bean = getObjectForBeanInstance(sharedInstance,name,mbd); } else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName,args); } finally { afterPrototypeCreation(beanName); } // 該方法是FactoryBean介面的呼叫入口 bean = getObjectForBeanInstance(prototypeInstance,mbd); } else { String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName,() -> { beforePrototypeCreation(beanName); try { return createBean(beanName,args); } finally { afterPrototypeCreation(beanName); } }); // 該方法是FactoryBean介面的呼叫入口 bean = getObjectForBeanInstance(scopedInstance,mbd); } }
在singleton作用域下,會呼叫getSingleton方法,然後回撥createBean建立物件,最終在getSingleton中完成快取;而當scope為prototype時,可以看到是直接呼叫了createBean方法並返回,沒有任何的快取操作,因此每次呼叫getBean都會建立新的物件,即使是同一個執行緒;除此之外都會進入到else片段中。
這個程式碼也很簡單,首先通過我們配置的scopeName從scopes中拿到對應的Scope物件,如SessionScope和RequestScope(但這兩個只會在Web環境中被載入,在WebApplicationContextUtils.registerWebApplicationScopes可以看到註冊操作),然後呼叫對應的get方法存到對應的request或session物件中去。程式碼很簡單,這裡就不分析了。
自定義Scope
通過以上分析,不難發現我們是很容易實現一個自己的Scope的,首先實現Scope介面,然後將我們類的例項新增到scopes快取中來,關鍵是怎麼新增呢?在AbstractBeanFactory類中有一個registerScope方法就是幹這個事的,因此我們只要拿到一個BeanFactory物件就行了,那要怎麼拿?還記得在refresh中呼叫的invokeBeanFactoryPostProcessors方法麼?因此我們只需要實現BeanFactoryPostProcessor介面就可以了,是不是So Easy!
BeanPostProcessor的執行時機
BeanPostProcessor執行點很多,根據其介面型別在不同的位置進行呼叫,只有熟記其執行時機,才能更好的進行擴充套件,這裡以一張時序圖來總結:
SpringBoot零配置實現原理淺析
在SpringBoot專案中,省去了大量繁雜的xml配置,只需要使用@ComponentScan、@Configuration以及@Bean註解就可以達到和使用xml配置的相同效果,大大簡化了我們的開發,那這個實現原理是怎樣的呢?熟悉了xml解析原理,相信對於這種註解的方式基本上也能猜個大概。
首先我們進入到AnnotationConfigApplicationContext類,這個就是註解方式的IOC容器:
public AnnotationConfigApplicationContext(String... basePackages) { this(); scan(basePackages); refresh(); } public AnnotationConfigApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); }
這裡ClassPathBeanDefinitionScanner在解析xml時出現過,就是用來掃描包找到合格的資源的;同時還建立了一個AnnotatedBeanDefinitionReader物件對應XmlBeanDefinitionReader,用來解析註解:
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry,Environment environment) { Assert.notNull(registry,"BeanDefinitionRegistry must not be null"); Assert.notNull(environment,"Environment must not be null"); this.registry = registry; this.conditionEvaluator = new ConditionEvaluator(registry,environment,null); AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( BeanDefinitionRegistry registry,@Nullable Object source) { DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry); if (beanFactory != null) { if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); } if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); } } Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8); if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry,def,CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry,AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); } // Check for JSR-250 support,and if present add the CommonAnnotationBeanPostProcessor. if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry,COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } // Check for JPA support,and if present add the PersistenceAnnotationBeanPostProcessor. if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(); try { def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,AnnotationConfigUtils.class.getClassLoader())); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,ex); } def.setSource(source); beanDefs.add(registerPostProcessor(registry,PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry,EVENT_LISTENER_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry,EVENT_LISTENER_FACTORY_BEAN_NAME)); } return beanDefs; }
在AnnotatedBeanDefinitionReader構造方法中可以看到呼叫了registerAnnotationConfigProcessors註冊一些列註解解析的Processor類,重點關注ConfigurationClassPostProcessor類,該類是BeanDefinitionRegistryPostProcessor的子類,所以會在refresh中呼叫,該類又會委託ConfigurationClassParser去解析@Configuration、@Bean、@ComponentScan等註解,所以這兩個類就是SpringBoot實現零配置的關鍵類,實現和之前分析的註解解析流程差不多,所以具體的實現邏輯讀者請自行分析。
回頭看當解析器和掃描器建立好後,同樣是呼叫scan方法掃描包,然後refresh啟動容器,所以實現邏輯都是一樣的,殊途同歸,只不過通過父子容器的構造方式使得我們可以很方便的擴充套件Spring。
總結
本篇是關於IOC實現的一些補充,最重要的是要理解迴圈依賴的解決辦法,其次SpringBoot零配置實現原理雖然這裡只是簡單起了個頭,但需要好好閱讀原始碼分析。另外還有很多細節,不可能全都講到,需要我們自己反覆琢磨,尤其是Bean例項化那一塊,這將是後面我們理解AOP的基礎。希望大家多多支援我們。