javascript
Spring —— 容器内部逻辑
引言
上一篇關于IoC容器的詳解《Spring —— IoC 容器詳解》真是工程浩大,可以說Spring官網對核心中的核心IOC容器做了非常全面的使用說明,包括在《Spring揭秘》中讓我一直沒有成功的Method Injection,官網也解決了我的疑惑,并最終實驗成功(未來會另起一篇單獨對“方法注入”做以總結)。
Spring官網的容器說明雖然全面,但是對于容器內部的處理并未深入解釋,因此本篇博客做理論性的補充,總結自王富強老師的《Spring揭秘》第四章——“容器背后的秘密”。而且,本篇文章在工作和面試中更具有理論性的指導意義。
一、概述
Spring容器通過某種方式加載xml(或注解、JavaConfig)中的配置數據,然后根據這些信息綁定整個系統的對象,最終組裝成一個可用的基于輕量級容器的應用系統。這個過程分為兩個階段,即容器啟動階段和Bean實例化階段:
1、容器啟動階段(四步:加載、解析、組裝BeanDefinition、注冊):
容器剛開始啟動時,首先加載配置,然后是解析并分析配置信息,將分析后的信息裝配到相應的BeanDefinition,最后把將BeanDefinition注冊到BeanDefinitionRegistry,啟動完成。
總的來說,該階段所做的工作可以認為是準備性的,重點更加側重于對象管理信息的收集,一些驗證性或輔助性的工作也可以在這個階段完成。
2、Bean實例化階段(四步:檢查、實例化、裝配、生命周期回調):
第一階段后,所有的bean定義信息都通過BeanDefinition的方式注冊到了BeanDefinitionRegistry中。當某個請求方通過getBean()方法明確地請求某個對象,或因依賴關系容器需要隱式地調用getBean方法時,就會觸發第二階段。
容器首先會檢查所請求的對象之前是否已經初始化。如果沒有,則會根據注冊的BeanDefinition所提供的信息實例化被請求對象,并為其注入依賴。如果該對象實現了某些回調接口,也會根據回調接口(Aware)的要求來裝配它。當該對象裝配完畢之后,容器會立即將其返回請求方使用。
二、容器啟動階段的擴展
Spring提供了一種叫做BeanFactoryPostProcessor的容器擴展機制。
該機制允許我們在容器實例化對象之前,對注冊到容器中的BeanDefinition進行修改。相當于在容器實現的第一階段最后加入一道工序,讓我們對最終的BeanDefinition做一些額外的操作,比如修改bean的某些屬性,為bean定義增加其他信息等。
如果要自定義BeanFactoryPostProcessor,通常就需要實現該接口,因為一個容器可能擁有多個后處理器,因此可能需要同時實現Ordered接口(如果順序確實必要)。因為Spring已經提供了幾個現成的后處理器實現,因此大多數時候我們很少去實現某個后處理器。其中PropertyPlaceholderConfigurer和PropertyOverrideConfigurer是兩個比較常用的BeanFactoryPostProcessor。
1、對于BeanFactory需要手動裝配BeanFactoryPostProcessor。
2、對于ApplicationContext,可以自動識別配置中的BeanFactoryPostProcessor。xml配置形式如下:
<bean class=”...PropertyPlaceholderConfigurer”> // 后處理器的一些屬性 </bean>三、bean的生命周期
容器在啟動之后,并不會馬上就實例化相應的bean定義。剛剛啟動的容器僅僅擁有所有對象的BeanDefinition來保存實例化階段將會用到的必要信息。
BeanFactory的getBean方法可以被客戶端對象顯式調用,也可以在容器內隱式調用。
隱式調用有以下兩種情況:
1、對于BeanFactory來說,對象實例化默認采用延遲初始化。A依賴B,如果當程序請求A對象時,容器會檢測A依賴的B是否已經實例化,如果沒有會隱式調用getBean實例化B對象,這對于本次請求者是隱式的。
2、ApplicationContext啟動之后會實例化所有的bean定義,在ApplicationContext的實現過程中,依然遵循Spring容器的兩個階段,只不過它會在啟動階段完成后,立刻調用所有bean定義的實例化方法getBean(這就是為什么當你得到ApplicationContext類型的容器引用時,容器內所有對象已經被全部實例化完成)。
(提示:AbstractBeanFactory類的getBean()方法的完整實現邏輯,和AbstractAutowiredCapableBeanFactory類的createBean()方法的全貌)
3.1 Bean的實例化策略與BeanWrapper
容器在內部實現的時候,采用“策略模式”來決定采用何種方式實例化bean。
通常可以通過反射或CGLIB動態字節碼生成來實例化相應的bean實例或動態生成其子類。
InstantiationStrategy接口定義了bean的實例化策略。
SimpleInstantiationStrategy實現了簡單的對象實例化功能,可以通過反射來實例化對象,但不支持方法注入方式的對象實例化。
CglibSubclassingInstantiationStrategy擴展了SimpleInstantiationStrategy,加入了CGLIB的動態字節碼生成功能,可以動態的生成某個類的子類,滿足了方法注入所需的對象實例化需求。這是容器默認采用的實例化策略。
容器只要根據相應的bean的BeanDefinition,和
CglibSubclassingInstantiationStrategy以及不同的bean定義類型,就可以返回實例化完成的對象實例。但不是直接返回構造完成的對象實例,而是以BeanWrapper對構造完成的bean進行了包裹,返回相應的BeanWrapper實例。到這里,第一步實例化結束。
BeanWrapper接口通常在Spring框架內部使用,它有一個實現類:BeanWrapperImpl,其作用是對某個bean進行“包裹”,然后對這個“包裹”的bean進行操作,比如設置或者獲取bean的相應屬性值。第一步結束后返回BeanWrapper實例而不是原先的對象實例,其目的就是為了第二步的“設置對象屬性”。
BeanWrapper繼承了PropertyAccessor接口,可以以統一的方式對對象屬性進行訪問,同時又繼承了PropertyEditorRegistry和TypeConverter接口(間接)。當把各種PropertyEditor注冊給容器時,后面就會被BeanWrapper用到。
在第一步構造完成對象之后,Spring會根據對象實例構造一個BeanWrapperImpl實例,然后將之前CustomEditorConfigurer注冊的PropertyEditor復制一份給BeanWrapperImpl實例,這就是為什么BeanWrapper同時也是PropertyEditorRegistry,這樣,BeanWrapper就可以完成類型轉換、設置對象屬性值等操作了。
以下是兩段分別通過BeanWrapper和Java反射API來設置對象屬性值和獲取屬性值的代碼片段,相比于Java反射API,Spring提供的BeanWrapper操作起來更加流程簡潔:
BeanWrapper方式:
Object provider = Class.forName(“package.name.FXNewsProvider”).newInstance(); Object listener = Class.forName(“...DowJonesNewsListener”).newInstance(); Object persister = Class.forName(“...DowJonesNewsPersister”).newInstance(); BeanWrapper newsProvider = new BeanWrapperImpl(provide); newsProvider.setPropertyValue(“newsListener”, listener); newsProvider.setPropertyValue(“newsPersister”, persister); assertTrue(newsProvider.getWrapperedInstance() instanceof FXNewsProvider); assertSame(provider, newsProvider.getWrapperedInstance()); assertSame(listener, newsProvider.getPropertyValue(“newsListener”));Java反射API方式:
Object provider = Class.forName(“package.name.FXNewsProvider”).newInstance(); Object listener = Class.forName(“...DowJonesNewsListener”).newInstance(); Object persister = Class.forName(“...DowJonesNewsPersister”).newInstance(); Class providerClazz= provider.getClass(); Field listenerField = providerClazz.getField(“newsListener”); listenerField.set(provider , listener);// 只演示listener屬性設置,persister類似 assertSame(listener, listenerField.get(provider));可以看出,Java反射API的方式在使用上相對混亂,且不便于記憶,而且還有緊隨其后的各種異常需要處理(上面并未寫出)。
3.2 Aware生命周期回調
當對象實例化完成并且相關屬性以及依賴設置完成后,spring 容器會檢查當前對象是否實現了一系列以Aware結尾的接口。如果是,則將Aware接口中規定的依賴注入給當前實例。
常見的Aware接口有:
1、BeanNameAware,容器會將bean定義對應的beanName設置到當前對象的實例。
2、BeanClassLoaderAware,容器會將對應加載當前bean的Classloader注入到當前對象實例。默認會使用加載springframework..ClassUtils類的Classloader。
3、BeanFactoryAware,BeanFactory容器會將自身設置到當前對象實例。當前對象就擁有了一個BeanFactory容器的引用,并且可以對這個容器內允許訪問的對象按照需要進行訪問。
這三個Aware接口只針對BeanFactory類型的容器而言,對于ApplicationContext類型的容器,也存在幾個Aware相關接口。
ApplicationContext檢測以下這些Aware接口并設置相關依賴的實現方式是通過BeanPostProcessor,這與前面的三個有所不同。不過設置Aware接口與BeanPostProcessor是相鄰的,也可以放在一起討論
1、ResourceLoaderAware,ApplicationContext實現了Spring的ResourceLoader接口,當容器檢測到對象實現了ResourceLoaderAware接口后,會將當前ApplicationContext自身設置到對象中,這樣當前對象就擁有了其所在ApplicationContext的一個引用。
2、ApplicationEventPublisherAware,ApplicationContext同樣實現了ApplicationEventPublisher接口,這樣,它就可以作為ApplicationEventPublisher來使用,所以,如果對象實現了ApplicationEventPublisherAware接口,容器就同樣會將自身注入當前對象。
3、ApplicationContextAware,和前面一樣,容器會將自身注入當前對象。
4、MessageSourceAware,和前面一樣,容器會將自身注入當前對象。
3.3?BeanPostProcessor
如何與BeanFactoryPostProcessor區分?
BeanPostProcessor存在于對象實例化階段,BeanFactoryPostProcessor存在于容器啟動階段。BeanFactoryPostProcessor會處理容器內所有符合條件的BeanDefinition,BeanPostProcessor會處理容器內所有符合條件的實例化后的對象實例。它包含兩個接口方法:
postProcessBeforeInitialization(Object bean, String beanName); postProcessAfterInitialization(Object bean, String beanName);常見的使用場景是處理標記接口實現類,或者為當前對象提供代理實現。
ApplicationContext對應的那些Aware接口實際上就是通過BeanPostProcessor的方式進行處理的。當ApplicationContext中每個對象的實例化過程走到BeanPostProcessor前置處理這一步時,容器會檢測到之前注冊到容器的ApplicationContextAwareProcessor這個BeanPostProcessor的實現類,然后調用其postProcessBeforeInitialization方法,檢查并設置Aware相關依賴:
if (bean instanceof EnvironmentAware) {((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) {((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); } if (bean instanceof ResourceLoaderAware) {((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } if (bean instanceof ApplicationEventPublisherAware) {((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); } if (bean instanceof MessageSourceAware) {((MessageSourceAware) bean).setMessageSource(this.applicationContext); } if (bean instanceof ApplicationContextAware) {((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); }3.4?自定義BeanPostProcessor
首先需要明確,BeanPostProcessor處理的是bean實例化階段的一個過程,因此需要處理的對象必須是bean,即必須注冊到ApplicationContext容器中,才能夠被該接口實現類處理。
第一步:明確需要執行的操作,比如需要提前為目標bean的某個屬性做轉化,或賦值。然后定義該功能的標記接口,提供可以被BeanPostProcessor訪問的接口方法。
第二步:讓目標bean實現標記接口。
第三步:實現BeanPostProcessor接口,并注冊到容器。通過instanceof關鍵字判斷目標bean是否為標記接口的子類,如果是,則進行相關處理邏輯,最后返回bean。
注意,標記接口和BeanPostProcessor接口本身不存在直接的繼承或依賴關系,另外,標記接口實際上并不是必須的,這是為了更好的限定處理邏輯的操作范圍而存在的,也就是說,我們可以通過自定義的BeanPostProcessor子類,通過instanceof直接判斷是否為目標bean類型的對象,然后直接處理,但這樣可能會在BeanPostProcessor中暴露太多無關于處理邏輯的屬性細節。標記接口很好的隔絕了BeanPostProcessor與目標bean類型之間的耦合。
3.5?InitializingBean和init-method
InitializingBean是容器內部廣泛使用的一個對象生命周期標識接口。
它只有一個接口方法:afterPropertiesSet()。其作用是在對象實例化過程調用過“BeanPostProcessor的前置處理”之后,會接著檢測當前對象是否實現了InitializingBean接口,如果是,則會調用afterPropertiesSet()方法進一步調整對象實例的狀態。
比如,在某些情況下,某個業務對象實例化完成后,還不能處于可以使用的狀態。這個時候就可以讓該業務對象實現該接口,并在方法afterPropertiesSet()中完成對該業務對象的后續處理。
實現這個接口會讓Spring框架帶有侵入性,因此init-method屬性可以替代“實現InitializingBean接口”完成初始化方法。另外,可以讓bean直接定義名為init()的方法,Spring容器也會在這一階段執行初始化邏輯。
一般,我們在集成第三方庫,或者其他特殊的情況下,才會需要使用該特性。
?
總結
以上是生活随笔為你收集整理的Spring —— 容器内部逻辑的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pgsql 前10条_未来3年,广州83
- 下一篇: Spring Cloud Alibaba