解析spring加載bean流程的方法
spring作為目前我們開(kāi)發(fā)的基礎(chǔ)框架,每天的開(kāi)發(fā)工作基本和他形影不離,作為管理bean的最經(jīng)典、優(yōu)秀的框架,它的復(fù)雜程度往往令人望而卻步。不過(guò)作為朝夕相處的框架,我們必須得明白一個(gè)問(wèn)題就是spring是如何加載bean的,我們常在開(kāi)發(fā)中使用的注解比如@Component、@AutoWired、@Socpe等注解,Spring是如何解析的,明白這些原理將有助于我們更深刻的理解spring。需要說(shuō)明一點(diǎn)的是spring的源碼非常精密、復(fù)雜,限于篇幅的關(guān)系,本篇博客不會(huì)細(xì)致的分析源碼,會(huì)采取抽絲剝繭的方式,避輕就重,抓住重點(diǎn)來(lái)分析整個(gè)流程(不會(huì)分析具體的細(xì)節(jié)),本次將會(huì)基于spring5.0的版本
一:spring讀取配置或注解的過(guò)程
1:先通過(guò)掃描指定包路徑下的spring注解,比如@Component、@Service、@Lazy @Sope等spring識(shí)別的注解或者是xml配置的屬性(通過(guò)讀取流,解析成Document,Document)然后spring會(huì)解析這些屬性,將這些屬性封裝到BeanDefintaion這個(gè)接口的實(shí)現(xiàn)類(lèi)中.
在springboot中,我們也可以采用注解配置的方式:
比如這個(gè)配置Bean,spring也會(huì)將className、scope、lazy等這些屬性裝配到PersonAction對(duì)應(yīng)的BeanDefintaion中.具體采用的是BeanDefinitionParser接口中的parse(Element element, ParserContext parserContext)方法,該接口有很多不同的實(shí)現(xiàn)類(lèi)。通過(guò)實(shí)現(xiàn)類(lèi)去解析注解或者xml然后放到BeanDefination中,BeanDefintaion的作用是集成了我們的配置對(duì)象中的各種屬性,重要的有這個(gè)bean的ClassName,還有是否是Singleton、對(duì)象的屬性和值等(如果是單例的話,后面會(huì)將這個(gè)單例對(duì)象放入到spring的單例池中)。spring后期如果需要這些屬性就會(huì)直接從它中獲取。然后,再注冊(cè)到一個(gè)ConcurrentHashMap中,在spring中具體的方法就是registerBeanDefinition(),這個(gè)Map存的key是對(duì)象的名字,比如Person這個(gè)對(duì)象,它的名字就是person,值是BeanDefination,它位于DefaultListableBeanFactory類(lèi)下面的beanDefinitionMap類(lèi)屬性中,同時(shí)將所有的bean的名字放入到beanDefinitionNames這個(gè)list中,目的就是方便取beanName;
二:spring的bean的生命周期
spring的bean生命周期其實(shí)最核心的分為4個(gè)步驟,只要理清三個(gè)關(guān)鍵的步驟,其他的只是在這三個(gè)細(xì)節(jié)中添加不同的細(xì)節(jié)實(shí)現(xiàn),也就是spring的bean生明周期:
實(shí)例化和初始化的區(qū)別:實(shí)例化是在jvm的堆中創(chuàng)建了這個(gè)對(duì)象實(shí)例,此時(shí)它只是一個(gè)空的對(duì)象,所有的屬性為null。而初始化的過(guò)程就是講對(duì)象依賴的一些屬性進(jìn)行賦值之后,調(diào)用某些方法來(lái)開(kāi)啟一些默認(rèn)加載。比如spring中配置的數(shù)據(jù)庫(kù)屬性Bean,在初始化的時(shí)候就會(huì)將這些屬性填充,比如driver、jdbcurl等,然后初始化連接
2.1:實(shí)例化 Instantiation
AbstractAutowireCapableBeanFactory.doCreateBean中會(huì)調(diào)用createBeanInstance()方法,該階段主要是從beanDefinitionMap循環(huán)讀取bean,獲取它的屬性,然后利用反射(core包下有ReflectionUtil會(huì)先強(qiáng)行將構(gòu)造方法setAccessible(true))讀取對(duì)象的構(gòu)造方法(spring會(huì)自動(dòng)判斷是否是有參數(shù)還是無(wú)參數(shù),以及構(gòu)造方法中的參數(shù)是否可用),然后再去創(chuàng)建實(shí)例(newInstance)
2.2:初始化
初始化主要包括兩個(gè)步驟,一個(gè)是屬性填充,另一個(gè)就是具體的初始化過(guò)程
2.2.1:屬性賦值 PopulateBean()會(huì)對(duì)bean的依賴屬性進(jìn)行填充,@AutoWired注解注入的屬性就發(fā)生這個(gè)階段,假如我們的bean有很多依賴的對(duì)象,那么spring會(huì)依次調(diào)用這些依賴的對(duì)象進(jìn)行實(shí)例化,注意這里可能會(huì)有循環(huán)依賴的問(wèn)題。后面我們會(huì)講到spring是如何解決循環(huán)依賴的問(wèn)題
2.2.2:初始化 Initialization
初始化的過(guò)程包括將初始化好的bean放入到spring的緩存中、填充我們預(yù)設(shè)的屬性進(jìn)一步做后置處理等
3: 使用和銷(xiāo)毀 Destruction
在Spring將所有的bean都初始化好之后,我們的業(yè)務(wù)系統(tǒng)就可以調(diào)用了。而銷(xiāo)毀主要的操作是銷(xiāo)毀bean,主要是伴隨著spring容器的關(guān)閉,此時(shí)會(huì)將spring的bean移除容器之中。此后spring的生命周期到這一步徹底結(jié)束,不再接受spring的管理和約束。
三:spring的BeanPostProcessor處理器
spring的另一個(gè)強(qiáng)大之處就是允許開(kāi)發(fā)者自定義擴(kuò)展bean的初始化過(guò)程,最主要的實(shí)現(xiàn)思路就是通過(guò)BeanPostProcessor來(lái)實(shí)現(xiàn)的,spring有各種前置和后置處理器,這些處理器滲透在bean創(chuàng)建的前前后后,穿插在spring生命周期的各個(gè)階段,每一步都會(huì)影響著spring的bean加載過(guò)程。接下來(lái)我們就來(lái)分析具體的過(guò)程:
3.1:實(shí)例化階段
該階段會(huì)調(diào)用對(duì)象的空構(gòu)造方法進(jìn)行對(duì)象的實(shí)例化,在進(jìn)行實(shí)例化之后,會(huì)調(diào)用InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法
BeanPostProcessor(具體實(shí)現(xiàn)是InstantiationAwareBeanPostProcessor). postProcessBeforeInstantiation();
這個(gè)階段允許在Bena進(jìn)行實(shí)例化之前,允許開(kāi)發(fā)者自定義邏輯,如返回一個(gè)代理對(duì)象。不過(guò)需要注意的是假如在這個(gè)階段返回了一個(gè)不為null的實(shí)例,spring就會(huì)中斷后續(xù)的過(guò)程。
BeanPostProcessor.postProcessAfterInstantiation();
這個(gè)階段是Bean實(shí)例化完畢后執(zhí)行的后處理操作,所有在初始化邏輯、裝配邏輯之前執(zhí)行
3.2:初始化階段
3.2.1:BeanPostProcessor.postProcessBeforeInitialization
該方法在bean初始化方法前被調(diào)用,Spring AOP的底層處理也是通過(guò)實(shí)現(xiàn)BeanPostProcessor來(lái)執(zhí)行代理邏輯的
3.2.2:InitializingBean.afterPropertiesSet
自定義屬性值該方法允許我們進(jìn)行對(duì)對(duì)象中的屬性進(jìn)行設(shè)置,假如在某些業(yè)務(wù)中,一個(gè)對(duì)象的某些屬性為null,但是不能顯示為null,比如顯示0或者其他的固定數(shù)值,我們就可以在這個(gè)方法實(shí)現(xiàn)中將null值轉(zhuǎn)換為特定的值
3.2.3:BeanPostProcessor.postProcessAfterInitialization(Object bean, String beanName)??梢栽谶@個(gè)方法中進(jìn)行bean的實(shí)例化之后的處理,比如我們的自定義注解,對(duì)依賴對(duì)象的版本控制自動(dòng)路由切換。比如有一個(gè)服務(wù)依賴了兩種版本的實(shí)現(xiàn),我們?nèi)绾螌?shí)現(xiàn)自動(dòng)切換呢?這時(shí)候可以自定義一個(gè)路由注解,假如叫@RouteAnnotaion,然后實(shí)現(xiàn)BeanPostProcessor接口,在其中通過(guò)反射拿到自定義的注解@RouteAnnotaion再進(jìn)行路由規(guī)則的設(shè)定。
3.2.4:SmartInitializingSingleton.afterSingletonsInstantiated
4.1:容器啟動(dòng)運(yùn)行階段
4.1.1:SmartLifecycle.start
容器正式渲染完畢,開(kāi)始啟動(dòng)階段,bean已經(jīng)在spring容器的管理下,程序可以隨時(shí)調(diào)用
5.1:容器停止銷(xiāo)毀
5.1.1:SmartLifecycle.stop(Runnable callback)
spring容器停止運(yùn)行
5.1.2:DisposableBean.destroy()
spring會(huì)將所有的bean銷(xiāo)毀,實(shí)現(xiàn)的bean實(shí)例被銷(xiāo)毀的時(shí)候釋放資源被調(diào)用
四:一些關(guān)鍵性的問(wèn)題
4.1:FactoryBean和BeanFactory的區(qū)別?
BeanFactory是個(gè)bean 工廠類(lèi)接口,是負(fù)責(zé)生產(chǎn)和管理bean的工廠,是IOC容器最底層和基礎(chǔ)的接口,spring用它來(lái)管理和裝配普通bean的IOC容器,它有多種實(shí)現(xiàn),比如AnnotationConfigApplicationContext、XmlWebApplicationContext等。
FactoryBean是FactoryBean屬于spring的一個(gè)bean,在IOC容器的基礎(chǔ)上給Bean的實(shí)現(xiàn)加上了一個(gè)簡(jiǎn)單工廠模式和裝飾模式,是一個(gè)可以生產(chǎn)對(duì)象和裝飾對(duì)象的工廠bean,由spring管理,生產(chǎn)的對(duì)象是由getObject()方法決定的。注意:它是泛型的,只能固定生產(chǎn)某一類(lèi)對(duì)象,而不像BeanFactory那樣可以生產(chǎn)多種類(lèi)型的Bean。在對(duì)于某些特殊的Bean的處理中,比如Bean本身就是一個(gè)工廠,那么在其進(jìn)行單獨(dú)的實(shí)例化操作邏輯中,可能我們并不想走spring的那一套邏輯,此時(shí)就可以實(shí)現(xiàn)FactoryBean接口自己控制邏輯。
4.2:spring如何解決循環(huán)依賴問(wèn)題
循環(huán)依賴問(wèn)題就是A->B->A,spring在創(chuàng)建A的時(shí)候,發(fā)現(xiàn)需要依賴B,因?yàn)槿?chuàng)建B實(shí)例,發(fā)現(xiàn)B又依賴于A,又去創(chuàng)建A,因?yàn)樾纬梢粋€(gè)閉環(huán),無(wú)法停止下來(lái)就可能會(huì)導(dǎo)致cpu計(jì)算飆升
如何解決這個(gè)問(wèn)題呢?spring解決這個(gè)問(wèn)題主要靠巧妙的三層緩存,所謂的緩存主要是指這三個(gè)map,singletonObjects主要存放的是單例對(duì)象,屬于第一級(jí)緩存;singletonFactories屬于單例工廠對(duì)象,屬于第三級(jí)緩存;earlySingletonObjects屬于第二級(jí)緩存,如何理解early這個(gè)標(biāo)識(shí)呢?它表示只是經(jīng)過(guò)了實(shí)例化尚未初始化的對(duì)象。Spring首先從singletonObjects(一級(jí)緩存)中嘗試獲取,如果獲取不到并且對(duì)象在創(chuàng)建中,則嘗試從earlySingletonObjects(二級(jí)緩存)中獲取,如果還是獲取不到并且允許從singletonFactories通過(guò)getObject獲取,則通過(guò)singletonFactory.getObject()(三級(jí)緩存)獲取。如果獲取到了則移除對(duì)應(yīng)的singletonFactory,將singletonObject放入到earlySingletonObjects,其實(shí)就是將三級(jí)緩存提升到二級(jí)緩存,這個(gè)就是緩存升級(jí)。spring在進(jìn)行對(duì)象創(chuàng)建的時(shí)候,會(huì)依次從一級(jí)、二級(jí)、三級(jí)緩存中尋找對(duì)象,如果找到直接返回。由于是初次創(chuàng)建,只能從第三級(jí)緩存中找到(實(shí)例化階段放入進(jìn)去的),創(chuàng)建完實(shí)例,然后將緩存放到第一級(jí)緩存中。下次循環(huán)依賴的再直接從一級(jí)緩存中就可以拿到實(shí)例對(duì)象了。
五:測(cè)試
我們來(lái)寫(xiě)一個(gè)測(cè)試類(lèi),驗(yàn)證一下上面的問(wèn)題:
5.1:首先聲明一個(gè)自定義的Bean
@Component public class CustomBean { public CustomBean(){ System.out.println("調(diào)用CustomBean空的構(gòu)造方法"); } }
5.2:聲明一個(gè)Bean來(lái)實(shí)現(xiàn)BeanPostProcessor
package com.wyq.spring.bean; import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import java.beans.PropertyDescriptor; /** * @Author: wyq * @Desc: * @Date: 2019/9/1 15:36 **/ @Component @Scope("singleton") public class TestBean implements BeanPostProcessor, SmartInitializingSingleton, InstantiationAwareBeanPostProcessor, DisposableBean{ private static final String BEAN_NAME= "customBean"; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (BEAN_NAME.equals(beanName)) { System.out.println("==>BeanPostProcessor.postProcessBeforeInitialization"); } return null; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (BEAN_NAME.equals(beanName)) { System.out.println("==>BeanPostProcessor.postProcessAfterInitialization"); } return null; } @Override public void afterSingletonsInstantiated() { System.out.println("==>SmartInitializingSingleton.afterSingletonsInstantiated"); } @Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { if (BEAN_NAME.equals(beanName)) { System.out.println("==>InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation"); } return null; } @Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { if (BEAN_NAME.equals(beanName)) { System.out.println("==>InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation"); } return false; } @Override public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { System.out.println("==>InstantiationAwareBeanPostProcessor.postProcessPropertyValues"); return null; } @Override public void destroy() throws Exception { System.out.println("==>DisposableBean.destroy"); } }
5.3:啟動(dòng)容器:
六:總結(jié)
本篇博客主要是介紹了Spring的一些實(shí)例化的過(guò)程,高屋建瓴的分析了一下spring的bean加載過(guò)程,沒(méi)有詳細(xì)展開(kāi)某個(gè)細(xì)節(jié)分析。spring的內(nèi)部源碼非常復(fù)雜,每個(gè)接口的實(shí)現(xiàn)類(lèi)都在5個(gè)以上,如果深入細(xì)節(jié),恐怕不是一篇博客能講清楚的。這篇博客的目的就是在闡述spring的基本脈絡(luò)中心路線順序,首先我們需要有一個(gè)總體的認(rèn)識(shí),然后再深入到細(xì)節(jié)就是輕而易舉的了。這也是一種學(xué)習(xí)的方法論,通過(guò)本篇博客我希望能梳理清楚spring的基本流程,對(duì)spring有一個(gè)比較清晰的認(rèn)識(shí)。并且學(xué)習(xí)到優(yōu)秀開(kāi)源框架的設(shè)計(jì)基本思想,還有就是進(jìn)一步提升自己的閱讀源碼的能力。
到此這篇關(guān)于解析spring加載bean流程的方法的文章就介紹到這了,更多相關(guān)spring加載bean內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于SpringBoot解決CORS跨域的問(wèn)題(@CrossOrigin)
這篇文章主要介紹了基于SpringBoot解決CORS跨域的問(wèn)題(@CrossOrigin),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01關(guān)于kafka消費(fèi)不到遠(yuǎn)程bootstrap-server?數(shù)據(jù)的問(wèn)題
很多朋友遇到kafka消費(fèi)不到遠(yuǎn)程bootstrap-server?數(shù)據(jù)的問(wèn)題,怎么解決這個(gè)問(wèn)題,很多朋友不知所措,下面小編給大家?guī)?lái)了關(guān)于kafka消費(fèi)不到遠(yuǎn)程bootstrap-server?數(shù)據(jù)的問(wèn)題及解決方法,感興趣的朋友跟隨小編一起看看吧2021-11-11System.currentTimeMillis()計(jì)算方式與時(shí)間的單位轉(zhuǎn)換詳解
這篇文章主要介紹了System.currentTimeMillis()計(jì)算方式與時(shí)間的單位轉(zhuǎn)換詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05java實(shí)現(xiàn)簡(jiǎn)單發(fā)送郵件功能
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單發(fā)送郵件功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Spring Cloud中FeignClient實(shí)現(xiàn)文件上傳功能
這篇文章主要為大家詳細(xì)介紹了Spring Cloud中FeignClient實(shí)現(xiàn)文件上傳功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04十分簡(jiǎn)單易懂的Java應(yīng)用程序性能調(diào)優(yōu)技巧分享
這篇文章主要介紹了十分簡(jiǎn)單易懂的Java性能調(diào)優(yōu)技巧分享,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11Java實(shí)現(xiàn)將圖片上傳到webapp路徑下 路徑獲取方式
這篇文章主要介紹了Java實(shí)現(xiàn)將圖片上傳到webapp路徑下 路徑獲取方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11