面試官問我Spring Bean,我一口氣...
前言
spring bean,其實經(jīng)常用spring的開發(fā)人員來說,這個單詞并不陌生,應該是相當熟悉,我們每天都會接觸到各種的bean對象,之前也介紹了,spring提供了IOC來完成bean的創(chuàng)建,讓我們大家不用new就可以直接拿到對象,使用對象了
我們來看一下spring bean的定義,spring官方文檔對于bean的解釋是:
- In Spring, the objects that form the backbone of your application
- and that are managed by the Spring IoC container are called beans.
- A bean is an object that is instantiated, assembled, and otherwise
- managed by a Spring IoC container.
翻譯過來就是:
在 Spring 中,構成應用程序主干并由Spring IoC容器管理的對象稱為bean。bean是一個由Spring IoC容器實例化、組裝和管理的對象。
概念很簡單明了,我們提取處關鍵的信息:
- bean是對象,一個或者多個不限定
- bean由Spring中一個叫IoC的東西管理
- 我們的應用程序由一個個bean構成
下面我們會從springbean的作用域、定義繼承、前置和后置處理器、生命周期(加載過程)等幾個方面來分析springbean,讓大家對其更加熟悉,面試直接起飛
作用域
springbean的作用域分為下面這幾種
1、singleton:單例作用域
2、prototype:每次從容器中調用Bean時,都會返回一個新的實例,即相當于執(zhí)行一次new的實例化操作
3、request:每次HTTP請求調用Bean時,spring容器都會創(chuàng)建一個新的Bean
4、session:同一個Http Session共享一個Bean,不同的session使用不同的bean
5、globalSession:全局session共享一個Bean,僅用于WebApplication環(huán)境
singleton:單例作用域
singleton在 Spring 容器中僅存在一個 Bean 實例, Bean 以單例的形式存在。
Spring 以容器的方式,使得我們僅需配置,即可得到天然的單例模式。
一般情況下,無狀態(tài)或者狀態(tài)不可變的類適合使用單例模式來實現(xiàn), 不過 Spring 利用 AOP 和 LocalThread 的能力,對非線程安全的變量(狀態(tài))進行了特殊處理,使的一些非線程安全的類(持有 Connection 的 DAO 類)變成了線程安全的類 。
因為 Spring 的超強能力,所以在實際應用中,大部分 Bean 都能以單例方式運行 ,這也是 bean 的默認作用域指定為 singleton 的原因 。
singleton 的 Bean 在同一個 Spring IoC 容器中只會一個實例。
prototype:每次從容器中調用Bean時,都會返回一個新的實例,即相當于執(zhí)行一次new的實例化操作
prototype 作用域的 bean 會導致在每次對該 bean 請求(將其注入到另一個 bean 中,或者以程序的方式調用容器的 getBean() 方法)時都會創(chuàng)建一個新的 bean 實例。
Prototype 是原型類型,它在我們創(chuàng)建容器的時候并沒有實例化,而是當我們獲取bean的時候才會去創(chuàng)建一個對象,而且我們每次獲取到的對象都不是同一個對象。
根據(jù)經(jīng)驗,對有狀態(tài)的 bean 應該使用 prototype 作用域,而對無狀態(tài)的bean則應該使用 singleton 作用域。
此外, Spring 容器將 prototype 的 bean 交給調用者后,就不再負責管理它的生命周期咯。
request:每次HTTP請求調用Bean時,spring容器都會創(chuàng)建一個新的Bean
每次 http 請求都會創(chuàng)建一個新的 Bean , 僅用于 WebApplicationContext 環(huán)境。request 作用域的 Bean 對應一個 HTTP 請求和生命周期 。
每次 HTTP 請求調用 Bean 時, Spring 容器就會創(chuàng)建一個新的 Bean ;請求處理完畢,就會銷毀這個 Bean。
session:同一個Http Session共享一個Bean,不同的session使用不同的bean
同一個 http Session 共享一個 Bean ,不同的 http Session 使用不同的 Bean,僅用于 WebApplicationContext 環(huán)境。
Bean 的作用于橫跨整個 HTTP Session。Session 中的所有 HTTP 請求會共享同一個 Bean. 只有當 HTTP Session 結束后,Bean實例才會被銷毀 。
globalSession:全局session共享一個Bean,僅用于WebApplication環(huán)境
globalSession同一個全局 Session 共享一個 bean,用于 Porlet,僅用于 WebApplication 環(huán)境。
globalSession 的作用域類似于 session 作用域, 不過僅在 Portlet 的 Web 應用中使用 。Portlet 定義了全局 Session,它被組成 Portlet Web 應用的所有子 Portlet 共享。如果不在 Portlet 的 Web 應用下,globalSession 等價于 session
定義繼承和前置后置處理器
定義繼承:bean定義可以包含很多的配置信息,包含構造函數(shù)的參數(shù)、屬性值,容器的具體信息例如初始化方法,靜態(tài)工廠方法名等
子Bean可以繼承父Bean的配置數(shù)據(jù),當然也可以去重寫其中的值,或者添加值,Springbean的定義繼承和Java的類繼承無關,但是呢,道理是一樣的,我們可以定義一個父Bean來作為模板,然后多個子Bean就可以從父Bean中繼承所需的配置
接下來我們看前置處理器和后置處理器,顧名思義,前置,指的是實例化對象之前的處理。后置,指的是實例化對象之后的處理。
前置處理器
在Spring中的前置處理器的接口是BeanFactoryPostProcess,這個機制允許我們在實例化相應的對象之前,對注冊到容器中的BeanDefinition存儲信息進行相應的修改
拿到了Provider的信息之后就可以通過監(jiān)聽觸發(fā) Protocol# refer 了,具體調用哪個 protocol 還是得看 URL的協(xié)議的,我們看下這個內部DubboProtocol的refer
可以根據(jù)這個機制對Bean增加其它信息,修改Bean定義的某些屬性值。想自定義前置處理器需要實現(xiàn)BeanFactoryPostProcess接口。當一個容器存在多種前置處理的時候,可以讓前置處理器的實現(xiàn)類同時繼承Ordered接口,顧名思義,就是用來排序的,可以實現(xiàn)優(yōu)先級。
Spring容器提供了數(shù)種現(xiàn)成的前置處理器,常見的如:
PropertyPlaceholderConfigurer:允許在xml文件中使用占位符。將占位符代表的資源單獨配置到簡單的Properties文件中加載
PropertyOverrideConfigurer:不同于PropertyPlaceholderConfigurer的是,該類用于處理容器中的默認值覆為新值的場景
CustomEditorConfigurer:此前的兩個前置處理器處理的均是BeanDefinition.通過把BeanDefinition的數(shù)據(jù)修改達到目的。CustomEditorConfigurer沒有對BeanDefinition做任何變動。負責的是將后期會用到的信息注冊到容器之中。例如將類型轉換器注冊到BeanDefinition中。供BeanDefinition將獲取到的String類型參數(shù)轉換為需要的類型。
后置處理器
在Spring中的后置處理器是BeanPostProcessor接口
在Spring中的后置處理器是BeanPostProcessor接口
可以看到有兩個方法BeanBeforePostProcessor和BeanAfterPostProcessor,我們根據(jù)方法名也能猜出個大概,大概就是一個是前面執(zhí)行的,一個是后面執(zhí)行的咯
可是問題來了,我們上面不是看了一個前置處理器了嗎,為什么這里又來了一個before,那這個before和after是針對于什么來說的呢
這里的before和after是相對于對象的初始化來說的,上面的前置處理器和后置處理器是針對于對象的實例化,兩者的范圍是不一樣的
實例化就是我們常說的,創(chuàng)建一個Bean的過程,即調用Bean的構造函數(shù);而初始化的過程則是一個賦值的過程,即調用Bean的setter,設置Bean的屬性的過程
生命周期(加載過程)
springbean的生命周期,也就是加載過程,這應該也是和spring的循環(huán)依賴一樣,也是屬于面試常問的一個點了,不知道大家被問到過沒,反正我是被問到過,spring的話題本來就是面試非常愛問的一點,IOC和AOP這是經(jīng)常問的,也是屬于最基礎的
稍微涉及到源碼的部分,都會問到循環(huán)依賴的三級緩存怎么工作的,為什么不用兩級緩存,bean的生命周期等問題
廢話少說了,多學學數(shù)據(jù)結構和算法,多學學spring,通吃
我們來看下springbean的生命周期流程,可以分為幾個階段
1、實例化過程
2、后置處理和放入緩存(這一步是為了循環(huán)依賴)
3、初始化過程(屬性賦值)
4、銷毀過程
主要的邏輯是在doCreateBean()方法,其實源碼的注釋也很清晰,大家可以多去讀讀源碼,真的很不錯,大家沒事的時候其實不用刷太多無用的博客,當然我這是有用的,關注還是很重要的!畢竟關注了不迷路,當你下次找工作,還在為不知道該復習什么的時候,或者不知道該復習哪些知識點的時候,我這個號啊,是真香
重要的是重要的是重要的是!我還會把所有相關的技術文章都給匯總起來,放到了GitHub上,大家可以隨時閱讀
公眾號一關注,時常讀幾篇技術文章,還可以閱讀以下靈魂文章;GitHub地址一收藏,下次面試再也不愁,offer輕松拿到手,直接起飛
我把這個源碼給貼出來,其實有點多,大家不用去細讀,讀個大概的流程就可以了
這一個方法真的很長,也是主要流程,簡單看一下
首先就是實例化Bean,然后呢,就是和我們上面所說的后置處理器有關了,允許后置處理器去修改相應的屬性,接著是把這個實例Bean直接放入到緩存中,而且是很急切的放入到緩存中去了!
相信大家看夠我上一篇的spring循環(huán)依賴的同學應該都很熟悉,知道為啥這里要急切的放入到緩存中了,沒錯,就是為了解決spring中的循環(huán)依賴的問題的
接著就是屬性的設置和初始化過程了,這一階段主要是進行屬性的賦值咯,這里有的小伙伴有一個誤解,認為這一塊會為屬性分配內存空間,不是的。分配內存的操作是在實例化Bean的過程中,這個過程JVM就已經(jīng)完成了內存空間的分配了
最后一步就是銷毀咯,這一步實在也沒啥好說的了
好了,這應該就是要說的所有了,關于bean想了解更詳細的可以去讀源碼,真的很建議大家讀讀關鍵地方的源碼,很多小伙伴對于讀源碼也是很頭疼的,教大家一個好法子,大家在讀源碼的時候,可以先找準其中的關鍵地方,怎么找?谷歌,百度!
找到關鍵的地方,然后一步步的去研究源碼的設計流程和某些地方的細節(jié),千萬不要把每一個細節(jié)都要認真讀懂,真的沒啥必要,浪費時間,抓住重點,去讀那些關鍵的地方
下次準備跳槽的時候再也不用擔心該復習什么了啊!