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