精品專欄
繼上篇部落格 【死磕 Spring】—– 載入 bean 之 快取中獲取單例 bean,如果從單例快取中沒有獲取到單例 bean,則說明兩種情況:
-
該 bean 的 scope 不是 singleton
-
該 bean 的 scope 是 singleton ,但是沒有初始化完成
針對這兩種情況 Spring 是如何處理的呢?統一載入並完成初始化!這部分內容的篇幅較長,拆分為兩部分,第一部分主要是一些檢測、parentBeanFactory 以及依賴處理,第二部分則是各個 scope 的初始化。
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
}
// 省略很多程式碼
這段程式碼主要處理如下幾個部分:
-
檢測。若當前 bean 在建立,則丟擲 BeanCurrentlyInCreationException 異常。
-
如果 beanDefinitionMap 中不存在 beanName 的 BeanDefinition(即在 Spring bean 初始化過程中沒有載入),則嘗試從 parentBeanFactory 中載入。
-
判斷是否為型別檢查。
-
從 mergedBeanDefinitions 中獲取 beanName 對應的 RootBeanDefinition,如果這個 BeanDefinition 是子 Bean 的話,則會合併父類的相關屬性。
-
依賴處理。
檢測
在前面就提過,Spring 只解決單例樣式下的迴圈依賴,對於原型樣式的迴圈依賴則是丟擲 BeanCurrentlyInCreationException 異常,所以首先檢查該 beanName 是否處於原型樣式下的迴圈依賴。如下:
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
呼叫 isPrototypeCurrentlyInCreation()
判斷當前 bean 是否正在建立,如下:
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set>) curVal).contains(beanName))));
}
其實檢測邏輯和單例樣式一樣,一個“集合”存放著正在建立的 bean,從該集合中進行判斷即可,只不過單例樣式的“集合”為 Set ,而原型樣式的則是 ThreadLocal,prototypesCurrentlyInCreation 定義如下:
private final ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<>("Prototype beans currently in creation");
檢查父類 BeanFactory
若 containsBeanDefinition 中不存在 beanName 相對應的 BeanDefinition,則從 parentBeanFactory 中獲取。
// 獲取 parentBeanFactory
BeanFactory parentBeanFactory = getParentBeanFactory();
// parentBeanFactory 不為空且 beanDefinitionMap 中不存該 name 的 BeanDefinition
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// 確定原始 beanName
String nameToLookup = originalBeanName(name);
// 若為 AbstractBeanFactory 型別,委託父類處理
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// 委託給建構式 getBean() 處理
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
// 沒有 args,委託給標準的 getBean() 處理
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
整個過程較為簡單,都是委託 parentBeanFactory 的 getBean()
進行處理,只不過在獲取之前對 name 進行簡單的處理,主要是想獲取原始的 beanName,如下:
protected String originalBeanName(String name) {
String beanName = transformedBeanName(name);
if (name.startsWith(FACTORY_BEAN_PREFIX)) {
beanName = FACTORY_BEAN_PREFIX + beanName;
}
return beanName;
}
transformedBeanName()
是對 name 進行轉換,獲取真正的 beanName,因為我們傳遞的可能是 aliasName(這個過程在部落格 【死磕 Spring】----- 載入 bean 之 開啟 bean 的載入 中分析 transformedBeanName()
有詳細說明),如果 name 是以 “&” 開頭的,則加上 “&”,因為在 transformedBeanName()
將 “&” 去掉了,這裡補上。
型別檢查
引數 typeCheckOnly 是用來判斷呼叫 getBean()
是否為型別檢查獲取 bean。如果不是僅僅做型別檢查則是建立bean,則需要呼叫 markBeanAsCreated()
記錄:
protected void markBeanAsCreated(String beanName) {
// 沒有建立
if (!this.alreadyCreated.contains(beanName)) {
// 加上全域性鎖
synchronized (this.mergedBeanDefinitions) {
// 再次檢查一次:DCL 雙檢查樣式
if (!this.alreadyCreated.contains(beanName)) {
// 從 mergedBeanDefinitions 中刪除 beanName,
// 併在下次訪問時重新建立它。
clearMergedBeanDefinition(beanName);
// 新增到已建立bean 集合中
this.alreadyCreated.add(beanName);
}
}
}
}
獲取 RootBeanDefinition
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
呼叫 getMergedLocalBeanDefinition()
獲取相對應的 BeanDefinition,如下:
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
// 快速從快取中獲取,如果不為空,則直接傳回
RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
if (mbd != null) {
return mbd;
}
// 獲取 RootBeanDefinition,
// 如果傳回的 BeanDefinition 是子類 bean 的話,則合併父類相關屬性
return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
}
首先直接從 mergedBeanDefinitions 快取中獲取相應的 RootBeanDefinition,如果存在則直接傳回否則呼叫 getMergedBeanDefinition()
獲取 RootBeanDefinition,若獲取的 BeanDefinition 為子 BeanDefinition,則需要合併父類的相關屬性。
處理依賴
如果一個 bean 有依賴 bean 的話,那麼在初始化該 bean 時是需要先初始化它所依賴的 bean。
// 獲取依賴。
// 在初始化 bean 時解析 depends-on 標簽時設定
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
// 迭代依賴
for (String dep : dependsOn) {
// 檢驗依賴的bean 是否已經註冊給當前 bean 獲取其他傳遞依賴bean
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
// 註冊到依賴bean中
registerDependentBean(dep, beanName);
try {
// 呼叫 getBean 初始化依賴bean
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
這段程式碼邏輯是:透過迭代的方式依次對依賴 bean 進行檢測、校驗,如果透過則呼叫 getBean()
實體化依賴 bean。
isDependent()
是校驗該依賴是否已經註冊給當前 bean。
protected boolean isDependent(String beanName, String dependentBeanName) {
synchronized (this.dependentBeanMap) {
return isDependent(beanName, dependentBeanName, null);
}
}
同步加鎖給 dependentBeanMap 物件,然後呼叫 isDependent()
校驗。dependentBeanMap 物件儲存的是依賴 beanName 之間的對映關係:beanName - > 依賴 beanName 的集合
private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) {
// alreadySeen 已經檢測的依賴 bean
if (alreadySeen != null && alreadySeen.contains(beanName)) {
return false;
}
// 獲取原始 beanName
String canonicalName = canonicalName(beanName);
// 獲取當前 beanName 的依賴集合
Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
// 不存在依賴,傳回false
if (dependentBeans == null) {
return false;
}
// 存在,則證明存在已經註冊的依賴
if (dependentBeans.contains(dependentBeanName)) {
return true;
}
// 遞迴檢測依賴
for (String transitiveDependency : dependentBeans) {
if (alreadySeen == null) {
alreadySeen = new HashSet<>();
}
alreadySeen.add(beanName);
if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
return true;
}
}
return false;
}
如果校驗成功,則呼叫 registerDependentBean()
將該依賴進行註冊,便於在銷毀 bean 之前對其進行銷毀。
public void registerDependentBean(String beanName, String dependentBeanName) {
String canonicalName = canonicalName(beanName);
synchronized (this.dependentBeanMap) {
Set<String> dependentBeans =
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
if (!dependentBeans.add(dependentBeanName)) {
return;
}
}
synchronized (this.dependenciesForBeanMap) {
Set<String> dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
dependenciesForBean.add(canonicalName);
}
}
其實將就是該對映關係儲存到兩個集合中:dependentBeanMap、dependenciesForBeanMap。
最後呼叫 getBean()
實體化依賴 bean。
至此,載入 bean 的第二個部分也分析完畢了,下篇開始分析第三個部分:各大作用域 bean 的處理。
【死磕 Spring】----- IOC 之 IOC 初始化總結
【死磕 Spring】----- IOC 之解析 bean 標簽:解析自定義標簽
【死磕 Spring】----- IOC 之解析 bean 標簽:constructor-arg、property 子元素
【死磕 Spring】—– IOC 之解析 bean 標簽:meta、lookup-method、replace-method)
【死磕 Spring】----- IOC 之解析 bean 標簽:開啟解析行程
【死磕 Spring】----- IOC 之 獲取 Document 物件
【死磕 Spring】----- IOC 之 註冊 BeanDefinition
【死磕 Spring】----- IOC 之 Spring 統一資源載入策略
【死磕 Spring】----- IOC 之深入理解 Spring IoC
END
我是 Java 技術驛站,感謝有你
>>>>>> 加群交流技術 <<<<<<