作為 Java 程式設計師肯定避不開 Spring 相關的知識,而 Spring 容器對 Bean 的管理又是 Spring 功能中重要的一部分。作為初學者往往會對此存在一些疑惑,或者對從書上、網際網路上看到的知識不能有個整體的認識。本篇文章將 Spring Bean 相關的知識做了個簡單的大串講,希望對初學者有所幫助。“綜述”這個詞參考論文的取名,你可以理解成綜合描述,也可以理解成泛泛而談。
何為 Bean ,何為 Spring 容器?
面嚮物件語言在使用一個物件的時候分為兩個步驟:
- 定義一個類 Class A
- 通過new 例項化一個 A 的物件。
然後才能去使用這個物件。在 C++ 中我們使用一個物件的時候,都是自己去手動 new 一個。當然原生的 Java 也是通過 new去 新建物件,只不過這種方式需要程式設計師手動管理物件,不僅麻煩而且增加程式碼量。Spring 便應用而生了,Spring 的兩大核心功能包括 IOC(依賴注入)和 AOP(面向切面程式設計)。IOC 說通俗點就是 Spring 幫我們管理類的物件,程式設計師想使用一個類的物件的時候不需要再主動去 new,只需要通過幾種方式比如註解讓 Spring 幫我們生成就可以了。要用的時候直接向 Spring 索取就好了。 所謂 Bean 就是 Spring 幫我們生成的物件,英文單詞 Bean 是豌豆的意思,這樣看來 Spring 像不像一個容器而各個物件就是各種豌豆呀。Spring 容器裡面盛著各種 Bean(豌豆),這下明白何為Bean ,何為 Spring 容器了吧。
如何告訴 Spring 替我們生成 Bean?
定義一個類後,如何讓 Spring 幫我生成 Bean 呢?主要包括以下三種方式:
- 在 xml 中進行顯示配置:Spring 最原始的生成 Bean 的方式,使用者只需要將類生成 Bean 的資訊配置到 XML 中,即可通過 Spring 生成 Bean。而當在程式中需要的 Bean 很多的時候,xml 配置檔案將多的很難管理。這種方式已經逐漸被(2)(3)替代了。
- Spring 隱式發現和自動裝配:只需要在類定義上加上@Compoennt、@Service、@Controller 等註解即可讓 Spring 發現並生成 Bean,當然 Spring 要通過 @ComponentScan 開啟自動掃描。這是目前最常用的方式。
- 在 Java 中進行顯示裝配:通過@Configuration 註解和 @Bean 顯示的告訴 Spring 幫我們生成 Bean。很多初學者可能一開始不知道這種方式在何種情況下被用到。其實我總結下:當你發現方法 2 實現不了,又想用 new 的時候(但是 new 又不能讓 Spring 幫我管理 Bean 了),就可以用這種方式。
Spring 生成 Bean 的流程是怎樣的?
手動生成物件很簡單,直接 new 一下就可以了。Spring 幫我們生成 Bean 也只是簡單的 new 一下嗎?一起看下面這張圖:
可以看到,Spring 生成 Bean 不只是簡單 new 這麼簡單,它要做的事情很多。具體的流程中每個節點的作用網上有很多文章講的比我好,就不班門弄斧了。個人覺得可以參考 Spring Bean的生命週期(非常詳細)這篇文章。我這裡只想告訴大家掌握 Bean 的生命週期的才能更好的使用 Bean,因為很多時候我們需要在特定的流程中做一些特定的事情。當然了,面試的時候也會經常問到 Spring 的生命週期相關的問題,所以,記住它吧。
同一個類 Spring 幫我們生成幾個 Bean ?
在 new 一個類的物件的時候,我們有時候採用單例模式,也就是整個程式中該類的物件始終只有一個;有時候我們又採用多例模式,想要就 new 一個。那 Spring 幫我們生成 Bean 是採用哪種模式呢?當然這個得我們告訴 Spring,通過@Scope註解指定下就好了。Spring 生成 Bean 有五種方式分別是:
- singleton: 單例模式,這也是 Spring 預設生成 Bean 的方式
- prototype: 原型模式,每次 getBean 之後都會生成一個新的 Bean。但是需要注意的一點是獲取 Bean 後 Spring 不在管理這個 Bean ,需要使用者(程式設計師)自己去管理。需要使用者自己去管理並不代表要手動釋放該 Bean,而是隻不能再從Spring 索取該物件了。
- request: request 表示針對每一次 HTTP 請求都會產生一個新的bean,同時該 bean 僅在當前 HTTP request 內有效。
- session: session 作用域表示針對每一次HTTP請求都會產生一個新的bean,同時該 Bean 僅在當前 http session 中有效。這種方式 Bean 的存活時間比(3)長,可以說(4)包含(3)。
- global session: global session 作用域類似於標準的 http session作用域,不過它僅僅在基於 portlet 的 web 應用中才有意義。portlet 規範定義了 global session 的概念,它被所有構成某個 portlet web 應用的各種不同的 portlet 所共享。在 global session 作用域中定義的bean被限定於全域性 portlet Session的生命週期範圍內。如果你在web中使用global session作用域來標識bean,那麼web會自動當成session型別來使用。
3/4/5 主要針對 web 程式的,一般的 Spring 應用程式只需要用到 1 和 2 就好了。
Spring 容器中的 Bean,該如何索取?
很多時候,Spring 幫我們生成的 Bean ,在相應的地方通過 @Autowire 或者其他註解即可。但有的時候我們想從 Spring 容器中獲取 Bean 並用自己的程式碼儲存起來,不通過 @Autowire 的方式自動注入而是手動選擇。在用 Spring 中使用工廠模式的時候,就能體會到從 Spring 獲取 Bean 的重要性。當然,從 Spring 獲取 Bean 有多種方式,可以參考下 Spring在程式碼中獲取bean的幾種方式 這篇文章。我一般使用繼承 ApplicationContextAware 這種方式。這個網上也有很多現成的工具類,不在贅述。
在這裡想要強調的是,什麼時機從 Spring 中索取 Bean 最合適。之前踩過的一個坑:在 B 的建構函式中獲取想要類 A 的 Bean,出現了 NPE(null pointer exception) 。從 Spring 管理 Bean 生命週期的那張圖可以看出,Spring 先執行所有類的建構函式來例項化所有 Bean。如果你在類 B 的建構函式中獲取類 A 的 Bean,這個時候類 A 的物件極大可能還沒例項化呢,所以出現 NPE,而在例項化之後的流程中獲取 A 類 Bean 就不會有這個問題。比如繼承InitialzationBean,在 afterPropertiesSet() 中獲取 A 的 Bean 即可。
主題圖片來自於網路,侵刪!