Bean作用域
名稱 | 作用域 |
---|---|
singleton | 單例物件,預設值的作用域 |
prototype | 每次獲取都會建立一個新的 Bean 例項 |
request | 每一次 HTTP 請求都會產生一個新的 Bean,該 Bean 僅當前 HTTP request 內有效 |
session | 在一次 HTTP session 中,容器將返回同一個例項 |
global-session | 將物件存入到 web 專案叢集的 session 域中,若不存在叢集,則 global session 相當於 session |
預設作用域是 Sigleton,多執行緒訪問同一個 Bean 時會存線上程不安全問題
保障執行緒安全方法:
- 在 Bean物件中儘量避免定義可變的成員變數(不太實際)
- 在類中定義一個 ThreadLocal 成員變數,將需要的可變成員變數儲存在 ThreadLocal 中
啟動初始化
@PostConstruct:
在 Bean 建立期間由 Spring 呼叫的初始化方法
- 使用:將 @PostConstruct 註解新增到方法上就行
InitializingBean:
實現 InitializingBean 介面,讓 Spring 呼叫某一個初始化方法
Controller 和 Service 是否執行緒安全的
預設情況下,Scope 值是單例(Singleton)的,是執行緒不安全的。
儘量不要在 @Controller / @Service 等容器中定義靜態變數,不論是單例還是多例都是執行緒不安全的。
BeanFactoryPostProcessor 和 BeanPostProcessor
BeanFactoryPostProcessor:
- 在 Bean 工程例項化 Bean 之前對 Bean 的定義進行修改
- ta可以讀取和修改 Bean的定義後設資料,例如修改 Bean 的屬性值、新增額外的配置資訊等
BeanPostProcessor:
- 在 Bean 例項化後對 Bean 進行和增強或修改
- ta可以在 Bean 的初始化過程中對 Bean 進行後處理,例如對 Bean 進行代理、新增額外的功能等
Bean 的生命週期
Spring 的生命週期大致分為:建立 -> 屬性填充 -> 初始化 Bean -> 使用 -> 銷燬 幾個核心階段
- 建立階段:主要是建立物件,物件的建立權交由 Spring 管理
- 屬性填充階段:主要是進行依賴的注入,將當前物件以來的 Bean 物件,從 Spring 容器中拿出,然後填充到對應的屬性中
- 初始化 Bean 階段:包括回撥各種 Aware 介面、回撥各種初始化方法、生成 AOP 代理物件也在該階段進行,該階段主要是完成初始化回撥
- 使用 Bean 階段:主要是 Bean 建立完成,在程式執行期間,提供服務的階段
- 銷燬 Bean 階段:主要是容器關閉或停止服務,對 Bean 進行銷燬處理
迴圈依賴
迴圈依賴指:一個例項或多個例項存在相互依賴的關係(類之間迴圈巢狀引用)
參考如下程式碼:
public class AService {
private BService bService;
}
public class BService {
private AService aService;
}
Bean 的建立步驟:
在建立 Bean 之前,Spring 會透過掃描獲取 BeanDefinition,
BeanDefinition 就緒後會讀取 BeanDefinition 中所對應的 class 來載入類。
例項化階段:根據建構函式來完成例項化(沒有屬性注入以及初始化的物件(原始物件))
屬性注入階段:對 Bean 的屬性進行依賴注入。
若 Bean 的某個方法有 AOP 操作,則需要根據原始物件生成 代理物件。
- AOP 代理是 BeanPostProcessor 實現的,而 BeanPostProcessor 是發生在屬性注入階段後的。
最後把代理物件放入單例池(一級快取 singletonObjects)中。
為什麼 Spring Bean 會產生迴圈依賴問題?
以參考程式碼分析:
當 AService 建立時,會對 AService 例項化生成一個原始物件,
然後在進行屬性注入時發現了需要 BService 對應的 Bean,
此時就會去 BService 進行建立,在 BService 例項化後生成一個原始物件後進行屬性注入,此時會發現也需要 AService 對應的 Bean。
三大迴圈依賴問題場景:
1、單例作用下的 Setter 方法注入/Field 屬性注入出現的迴圈依賴
- 在 Spring 中 是不會 產生迴圈依賴問題的,這主要是靠 三級快取 機制解決
2、單例作用域下的構造器注入出現的迴圈依賴
- 因 構造注入 發生在 例項化階段,而 Spring 解決迴圈依賴問題依靠的 三級快取 是在 屬性注入階段,也就是說呼叫建構函式時還未能放入三級快取中,所以無法解決 構造器注入 的迴圈依賴問題
3、原型作用域下的屬性注入出現的迴圈依賴問題
- 因 Spring 不會快取 原型 作用域的 Bean,所以 Spring 無法解決 原型 作用域的 Bean
三級快取:
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory> singletonFactories = new ConcurrentHashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
- 一級快取(singletonObjects):快取的是 已經例項化、屬性注入、初始化後的 Bean物件
- 二級快取(earlySingletonObjects):快取的是例項化後,但未屬性注入、初始化的 Bean物件(用於提前暴露 Bean)
- 三級快取(singletonFactories):快取的是一個 ObjectFactory,主要作用是生成原始物件進行 AOP 操作後的代理物件(這一級快取主要用於解決 AOP 問題)
為什麼快取可以解決迴圈依賴問題?
在建立 AService 時,例項化後將 原始物件 存放到快取中(提早暴露),
然後依賴注入時發現需要 BService,便會去建立 BService,例項化後同樣 原始物件 存放到快取中,然後依賴注入時發現需要 AService 便會從快取中取出並注入,這樣 BService 就完成了建立,隨後 AService 也能完成屬性注入,最後也完成建立。
為什麼還需要第三級快取?
第三級快取(singletonFactories)是為了 Spring 的 AOP 做處理的。
1、如果 AService 中方法沒有使用 AOP 操作,會發現 BService 注入的原始物件與最後 *AService 完成建立的最終物件是 同一個物件。
2、如果 AService 方法中有 AOP 操作時,當 AService 的原始物件賦值(注入)給 BService,AService會進行 AOP 操作產生一個 代理物件,這個代理物件最終會被放入單例池(一級快取)中,也就是說此時 BService中注入的物件是原始物件,而 AService 最終建立完成後是代理物件,這樣就會導致 Bservice 依賴的 AService 和 最終的 AService 不是同一個物件,
出現這個問題的原因:AOP 是透過 BeanPostProcess 實現的,而 BeanPostProcessor 是在屬性注入階段後 才執行的,所以會導致 注入的物件有可能和最終的物件不一致
Spring 是如何透過第三級快取來避免 AOP 問題的?
三級快取透過利用 ObjectFactory 和 getEarlyBeanReference() 做到了提前執行 AOP 操作從而生成代理物件。
在上移到二級快取時,可以做到如果 Bean 中有 AOP 操作,那麼提前暴露的物件會是 AOP 操作後返回的代理物件;如果沒有 AOP 操作,那麼提前暴露的物件會是原始物件。
- 只有等完成了屬性注入、初始化後的 Bean 才會上移到一級快取(單例池)中
這樣就能做到出現迴圈依賴問題時,注入依賴的物件和最終生成的物件是同一個物件。
- 相當於 AOP 提前在屬性注入前完成,這樣就不會導致後面生成的代理物件與屬性注入時的物件不一致