深入解析Spring Bean初始化時(shí)和銷毀時(shí)的擴(kuò)展點(diǎn)
一.前言
今天來(lái)分享一下Bean在初始化時(shí)和Bean銷毀時(shí)我們可以做的一些操作,如果只是單純做CRUD開(kāi)發(fā),那么這些操作基本上不可能遇到,如果依賴于Spring來(lái)做一些框架層面的開(kāi)發(fā)或者中間件開(kāi)發(fā),那么這些操作是很常用的,在Bean進(jìn)行初始化或者銷毀的時(shí)候,如果我們需要做一些操作,比如加載和銷毀一些資源或者執(zhí)行一些方法時(shí),那么就可以使用Spring提供的一些擴(kuò)展,今天主要分享初始化Bean時(shí)的三種方式和銷毀Bean時(shí)的三種方式。
二.相關(guān)擴(kuò)展點(diǎn)和方法
初始化時(shí)和銷毀時(shí)都有相應(yīng)的方式供我們選擇,下面列出了初始化時(shí)和銷毀時(shí)的各三種方式,然后再進(jìn)行深度解析。
初始化時(shí)
- @PostConstruct
- 自定義初始化方法
- InitializingBean
銷毀時(shí)
- @PreDestroy
- 自定義銷毀方法
- DisposableBean
三.測(cè)試
定義Bean
下面我們定義了一個(gè)Bean,實(shí)現(xiàn)了InitializingBean和DisposableBean接口,分別在方法上使用了@PostConstruct注解和@PreDestroy注解,又自定義了初始化方法initMethod()和銷毀方法destoryMethod()。

配置Bean
使用@Configuration注解和@Bean注解來(lái)注冊(cè)Bean,我們?cè)贗nitDestroyBean上使用了@Bean注解來(lái)將其標(biāo)注為一個(gè)Bean,并且加上了初始化方法和銷毀方法。

查看結(jié)果

從控制臺(tái)輸出我們可以得出這些方式的優(yōu)先級(jí)
@PostConstruct > InitializingBean > 自定義初始化方法
@PreDestroy > DisposableBean > 自定義銷毀方法
四.源碼解析
下面進(jìn)行源碼解析,因?yàn)镾pring的源碼還是比較復(fù)雜,所以我們只從最關(guān)鍵的地方開(kāi)始分析,下分析初始化Bean時(shí),再分析銷毀Bean時(shí)。
初始化Bean
1.解析Bean中@PostConstruct注解和@PreDestory注解
我們直接來(lái)到AbstractAutowireCapableBeanFactory類中,@PostConstruct注解和@PreDestory標(biāo)注的方法會(huì)在applyMergedBeanDefinitionPostProcessors中被后置處理器InitDestroyAnnotationBeanPostProcessor解析,原理是通過(guò)反射判斷Bean中是否有方法上標(biāo)注了@PostConstruct注解和@PreDestory注解,如果有,則將其加入initMethods和destroyMethods集合中,然后組裝到LifecycleMetadata中,以供后續(xù)使用。

2.對(duì)Bean進(jìn)行初始化-調(diào)用標(biāo)注@PostConstruct的方法
下一步進(jìn)入initializeBean方法中,然后進(jìn)入applyBeanPostProcessorsBeforeInitialization方法,從名字我們可以看出這是Bean初始化前操作,這里會(huì)調(diào)用InitDestroyAnnotationBeanPostProcessor后置處理進(jìn)行處理。

從上面可以看出會(huì)通過(guò)findLifecycleMetadata去獲取元數(shù)據(jù),就是上面我們說(shuō)的LifecycleMetadata,這里會(huì)用到,然后調(diào)用invokeInitMethods方法,最終會(huì)通過(guò)反射調(diào)用到標(biāo)注了@PostConstruct注解的方法。

從上面我們可以看出,標(biāo)注了@PostConstruct注解的方法最先執(zhí)行。
3.調(diào)用自定義初始化方法和實(shí)現(xiàn)了InitializingBean接口的方法。
接著調(diào)用invokeInitMethods方法,里面會(huì)判斷Bean是否實(shí)現(xiàn)了InitializingBean接口,如果實(shí)現(xiàn),那么就會(huì)調(diào)用方法afterPropertiesSet(),接著會(huì)獲取Bean中自定義的初始化方法,然后通過(guò)反射調(diào)用。

從上面看出,實(shí)現(xiàn)了InitializingBean接口中的最先被執(zhí)行,自定義的Bean初始化方法第二被執(zhí)行。
4.總結(jié)
從上面看出,如果是通過(guò)@PostConstruct注解標(biāo)注的方法,則需要使用后置處理器BeanPostProcessor來(lái)進(jìn)行處理,實(shí)現(xiàn)InitializingBean接口和自定義的初始化方法則不需要使用后置處理器處理,@PostConstruct標(biāo)注的方法的優(yōu)先級(jí)大于實(shí)現(xiàn)了InitializingBean接口的方法,實(shí)現(xiàn)了InitializingBean接口的方法大于自定義的初始化方法。
銷毀Bean
銷毀Bean的動(dòng)作發(fā)生在容器關(guān)閉的時(shí)候,當(dāng)Spring程序中發(fā)生BeansException異常是會(huì)觸發(fā),還有我們也可以手動(dòng)關(guān)閉容器,關(guān)閉容器后,Spring中的所有Bean都會(huì)被清理掉,這時(shí)候如果再去獲取對(duì)應(yīng)的Bean,就會(huì)發(fā)生異常。

1.手動(dòng)關(guān)閉容器
為了去分析源碼,我們這里直接手動(dòng)去關(guān)閉Spring容器,直接調(diào)用close()方法關(guān)閉容器。

手動(dòng)調(diào)用關(guān)閉容器后,會(huì)去調(diào)用doClose()方法,然后里面有一個(gè)destroyBeans()方法,這里方法就是銷毀Bean,我們可以看到它有一個(gè)備注Destroy all cached singletons in the context's BeanFactory.,意思就是銷毀單例Bean,至于為什么是銷毀單例Bean,大家可以想一下,哈哈!

2.執(zhí)行標(biāo)注了@PreDestroy的方法
順著源碼一直跟進(jìn)來(lái),我們發(fā)現(xiàn)它它也會(huì)調(diào)用Bean的后置處理器,然后通過(guò)反射調(diào)用標(biāo)注了@PreDestroy注解的方法,這里和標(biāo)注了@PostConstruct的方法的執(zhí)行是一樣的。

3.調(diào)用實(shí)現(xiàn)DisposableBean接口的方法
接著判斷當(dāng)前的Bean是否實(shí)現(xiàn)了DisposableBean接口,如果實(shí)現(xiàn)了,則調(diào)用destroy()方法,和InitializingBean也是一樣的套路。

4.執(zhí)行自定義的銷毀方法
往下執(zhí)行,就會(huì)判斷是否定義了自定義的銷毀方法,如果定義了,則通過(guò)反射進(jìn)行調(diào)用,和初始化方法哪里是一樣的套路。

5.總結(jié)
從上面可以看出,銷毀Bean和初始化Bean時(shí)這些擴(kuò)展點(diǎn)的方式基本上都差不多,在銷毀Bean時(shí),會(huì)將其中涉及到的裝Bean的一些集合都進(jìn)行清空,然后再把BeanFactory關(guān)閉,不過(guò)我們這里關(guān)注的時(shí)銷毀時(shí)執(zhí)行的方法,就不去管那些了。
我們得出結(jié)論,標(biāo)注了@PreDestroy注解的方法最先被執(zhí)行,實(shí)現(xiàn)了DisposableBean接口的第二被執(zhí)行,自定義的銷毀方法最后被執(zhí)行。
五.思考
我們思考一下,為什么Spring對(duì)于@PostConstruct注解和@PreDestory注解要使用專門(mén)的后置處理器來(lái)處理?
其實(shí)這也是Spring牛逼的地方,擴(kuò)展性極強(qiáng),因?yàn)锧PostConstruct注解和@PreDestory注解其實(shí)不是屬于Spring的,而是Java語(yǔ)言層面的注解,如果不通過(guò)擴(kuò)展的方式來(lái)實(shí)現(xiàn)這兩個(gè)注解的使用,那么就沒(méi)有擴(kuò)展性而言,加入那天再需要加入Java層面的注解到Spring中,那么又需要去代碼里面改,顯然,這樣的設(shè)計(jì)時(shí)不合理的。
像@Resource注解也不是Spring的,也是Java層面的,處理這個(gè)注解也時(shí)通過(guò)后置處理器來(lái)進(jìn)行處理。
所以Spring為什么發(fā)展得這么迅猛,Java程序員基本沒(méi)人不用Spring,Spring的不斷發(fā)展,使它成為最復(fù)雜的框架,但是它的內(nèi)核設(shè)計(jì)還是十分優(yōu)秀的,如果沒(méi)有優(yōu)秀的設(shè)計(jì),估計(jì)代碼已經(jīng)不堪入目了。
六.總結(jié)
上面我們對(duì)于Spring的Bean初始化時(shí)和銷毀時(shí)的一些操作進(jìn)行了介紹并進(jìn)行測(cè)試,然后分析了它們的原理,并對(duì)Spring的設(shè)計(jì)進(jìn)行我個(gè)人的理解和評(píng)價(jià)。
其實(shí)對(duì)于像Spring這樣龐大的框架,學(xué)習(xí)難度還是比較大的,需要我們一遍有一遍去debug,去分析,去理解,這樣才能慢慢對(duì)它有一個(gè)了解,如果只是為了去應(yīng)付,去背,那么基本上沒(méi)用。
以上就是深入解析Spring Bean初始化時(shí)和銷毀時(shí)的擴(kuò)展點(diǎn)的詳細(xì)內(nèi)容,更多關(guān)于Spring Bean初始化和銷毀擴(kuò)展點(diǎn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java中基本注解的知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家整理的是一篇關(guān)于java中基本注解的知識(shí)點(diǎn)總結(jié),有需要的朋友們可以跟著學(xué)習(xí)下。2021-06-06
SpringBoot錯(cuò)誤處理機(jī)制以及自定義異常處理詳解
這篇文章主要為大家詳細(xì)介紹了SpringBoot錯(cuò)誤處理機(jī)制以及自定義異常處理,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05
Idea開(kāi)發(fā)工具之SpringBoot整合JSP的過(guò)程
最近在學(xué)習(xí)SpringBoot,看到SpringBoot整合jsp,順帶記錄一下。本文通過(guò)圖文實(shí)例相結(jié)合給大家講解SpringBoot整合JSP的過(guò)程,感興趣的朋友一起看看吧2021-09-09
基于HttpClient在HTTP協(xié)議接口測(cè)試中的使用(詳解)
下面小編就為大家?guī)?lái)一篇基于HttpClient在HTTP協(xié)議接口測(cè)試中的使用(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
Java8 Zip 壓縮與解壓縮的實(shí)現(xiàn)
這篇文章主要介紹了Java8 Zip 壓縮與解壓縮的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
關(guān)于BufferedReader的讀取效率問(wèn)題
這篇文章主要介紹了關(guān)于BufferedReader的讀取效率問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12

