摘要:你也會了解到構造對象的兩種策略。構造方法參數數量低于配置的參數數量,則忽略當前構造方法,并重試。通過默認構造方法創建對象看完了上面冗長的邏輯,本節來看點輕松的吧通過默認構造方法創建對象。
1. 簡介
本篇文章是上一篇文章(創建單例 bean 的過程)的延續。在上一篇文章中,我們從戰略層面上領略了doCreateBean方法的全過程。本篇文章,我們就從戰術的層面上,詳細分析doCreateBean方法中的一個重要的調用,即createBeanInstance方法。在本篇文章中,你將看到三種不同的構造 bean 對象的方式。你也會了解到構造 bean 對象的兩種策略。如果你對這些內容感興趣,那么不妨繼續往下讀。我會在代碼進行大量的注解,相信能幫助你理解代碼邏輯。好了,其他的就不多說了,進入正題吧。
2. 源碼分析 2.1 創建 bean 對象的過程本節,我們一起來來分析一下本篇文章的主角createBeanInstance方法。按照慣例,我們還是先分析一下方法的大致脈絡,然后我們再按照這個脈絡去分析一些重要的調用。So. Let`s go → ↓
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { Class> beanClass = resolveBeanClass(mbd, beanName); /* * 檢測類的訪問權限。默認情況下,對于非 public 的類,是允許訪問的。 * 若禁止訪問,這里會拋出異常 */ if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean class isn"t public, and non-public access not allowed: " + beanClass.getName()); } /* * 如果工廠方法不為空,則通過工廠方法構建 bean 對象。這種構建 bean 的方式 * 就不深入分析了,有興趣的朋友可以自己去看一下。 */ if (mbd.getFactoryMethodName() != null) { // 通過“工廠方法”的方式構建 bean 對象 return instantiateUsingFactoryMethod(beanName, mbd, args); } /* * 當多次構建同一個 bean 時,可以使用此處的快捷路徑,即無需再次推斷應該使用哪種方式構造實例, * 以提高效率。比如在多次構建同一個 prototype 類型的 bean 時,就可以走此處的捷徑。 * 這里的 resolved 和 mbd.constructorArgumentsResolved 將會在 bean 第一次實例 * 化的過程中被設置,在后面的源碼中會分析到,先繼續往下看。 */ boolean resolved = false; boolean autowireNecessary = false; if (args == null) { synchronized (mbd.constructorArgumentLock) { if (mbd.resolvedConstructorOrFactoryMethod != null) { resolved = true; autowireNecessary = mbd.constructorArgumentsResolved; } } } if (resolved) { if (autowireNecessary) { // 通過“構造方法自動注入”的方式構造 bean 對象 return autowireConstructor(beanName, mbd, null, null); } else { // 通過“默認構造方法”的方式構造 bean 對象 return instantiateBean(beanName, mbd); } } // 由后置處理器決定返回哪些構造方法,這里不深入分析了 Constructor>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); /* * 下面的條件分支條件用于判斷使用什么方式構造 bean 實例,有兩種方式可選 - 構造方法自動 * 注入和默認構造方法。判斷的條件由4部分綜合而成,如下: * * 條件1:ctors != null -> 后置處理器返回構造方法數組是否為空 * * 條件2:mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR * -> bean 配置中的 autowire 屬性是否為 constructor * 條件3:mbd.hasConstructorArgumentValues() * -> constructorArgumentValues 是否存在元素,即 bean 配置文件中 * 是否配置了* 條件4:!ObjectUtils.isEmpty(args) * -> args 數組是否存在元素,args 是由用戶調用 * getBean(String name, Object... args) 傳入的 * * 上面4個條件,只要有一個為 true,就會通過構造方法自動注入的方式構造 bean 實例 */ if (ctors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { // 通過“構造方法自動注入”的方式構造 bean 對象 return autowireConstructor(beanName, mbd, ctors, args); } // 通過“默認構造方法”的方式構造 bean 對象 return instantiateBean(beanName, mbd); }
以上就是 createBeanInstance 方法的源碼,不是很長。配合著注釋,應該不是很難懂。下面我們來總結一下這個方法的執行流程,如下:
檢測類的訪問權限,若禁止訪問,則拋出異常
若工廠方法不為空,則通過工廠方法構建 bean 對象,并返回結果
若構造方式已解析過,則走快捷路徑構建 bean 對象,并返回結果
如第三步不滿足,則通過組合條件決定使用哪種方式構建 bean 對象
這里有三種構造 bean 對象的方式,如下:
通過“工廠方法”的方式構造 bean 對象
通過“構造方法自動注入”的方式構造 bean 對象
通過“默認構造方法”的方式構造 bean 對象
下面我將會分析第2和第3種構造 bean 對象方式的實現源碼。至于第1種方式,實現邏輯和第2種方式較為相似。所以就不分析了,大家有興趣可以自己看一下。
2.2 通過構造方法自動注入的方式創建 bean 實例本節,我將會分析構造方法自動注入的實現邏輯。代碼邏輯較為復雜,需要大家耐心閱讀。代碼如下:
protected BeanWrapper autowireConstructor( String beanName, RootBeanDefinition mbd, Constructor>[] ctors, Object[] explicitArgs) { // 創建 ConstructorResolver 對象,并調用其 autowireConstructor 方法 return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs); } public BeanWrapper autowireConstructor(final String beanName, final RootBeanDefinition mbd, Constructor>[] chosenCtors, final Object[] explicitArgs) { // 創建 BeanWrapperImpl 對象 BeanWrapperImpl bw = new BeanWrapperImpl(); this.beanFactory.initBeanWrapper(bw); Constructor> constructorToUse = null; ArgumentsHolder argsHolderToUse = null; Object[] argsToUse = null; // 確定參數值列表(argsToUse) if (explicitArgs != null) { argsToUse = explicitArgs; } else { Object[] argsToResolve = null; synchronized (mbd.constructorArgumentLock) { // 獲取已解析的構造方法 constructorToUse = (Constructor>) mbd.resolvedConstructorOrFactoryMethod; if (constructorToUse != null && mbd.constructorArgumentsResolved) { // 獲取已解析的構造方法參數列表 argsToUse = mbd.resolvedConstructorArguments; if (argsToUse == null) { // 若 argsToUse 為空,則獲取未解析的構造方法參數列表 argsToResolve = mbd.preparedConstructorArguments; } } } if (argsToResolve != null) { // 解析參數列表 argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve); } } if (constructorToUse == null) { boolean autowiring = (chosenCtors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); ConstructorArgumentValues resolvedValues = null; int minNrOfArgs; if (explicitArgs != null) { minNrOfArgs = explicitArgs.length; } else { ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues(); resolvedValues = new ConstructorArgumentValues(); /* * 確定構造方法參數數量,比如下面的配置: ** * * 此時 minNrOfArgs = maxIndex + 1 = 2 + 1 = 3,除了計算 minNrOfArgs, * 下面的方法還會將 cargs 中的參數數據轉存到 resolvedValues 中 */ minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues); } // 獲取構造方法列表 Constructor>[] candidates = chosenCtors; if (candidates == null) { Class> beanClass = mbd.getBeanClass(); try { candidates = (mbd.isNonPublicAccessAllowed() ? beanClass.getDeclaredConstructors() : beanClass.getConstructors()); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); } } // 按照構造方法的訪問權限級別和參數數量進行排序 AutowireUtils.sortConstructors(candidates); int minTypeDiffWeight = Integer.MAX_VALUE; Set* * * > ambiguousConstructors = null; LinkedList causes = null; for (Constructor> candidate : candidates) { Class>[] paramTypes = candidate.getParameterTypes(); /* * 下面的 if 分支的用途是:若匹配到到合適的構造方法了,提前結束 for 循環 * constructorToUse != null 這個條件比較好理解,下面分析一下條件 argsToUse.length > paramTypes.length: * 前面說到 AutowireUtils.sortConstructors(candidates) 用于對構造方法進行 * 排序,排序規則如下: * 1. 具有 public 訪問權限的構造方法排在非 public 構造方法前 * 2. 參數數量多的構造方法排在前面 * * 假設現在有一組構造方法按照上面的排序規則進行排序,排序結果如下(省略參數名稱): * * 1. public Hello(Object, Object, Object) * 2. public Hello(Object, Object) * 3. public Hello(Object) * 4. protected Hello(Integer, Object, Object, Object) * 5. protected Hello(Integer, Object, Object) * 6. protected Hello(Integer, Object) * * argsToUse = [num1, obj2],可以匹配上的構造方法2和構造方法6。由于構造方法2有 * 更高的訪問權限,所以沒理由不選他(盡管后者在參數類型上更加匹配)。由于構造方法3 * 參數數量 < argsToUse.length,參數數量上不匹配,也不應該選。所以 * argsToUse.length > paramTypes.length 這個條件用途是:在條件 * constructorToUse != null 成立的情況下,通過判斷參數數量與參數值數量 * (argsToUse.length)是否一致,來決定是否提前終止構造方法匹配邏輯。 */ if (constructorToUse != null && argsToUse.length > paramTypes.length) { break; } /* * 構造方法參數數量低于配置的參數數量,則忽略當前構造方法,并重試。比如 * argsToUse = [obj1, obj2, obj3, obj4],上面的構造方法列表中, * 構造方法1、2和3顯然不是合適選擇,忽略之。 */ if (paramTypes.length < minNrOfArgs) { continue; } ArgumentsHolder argsHolder; if (resolvedValues != null) { try { /* * 判斷否則方法是否有 ConstructorProperties 注解,若有,則取注解中的 * 值。比如下面的代碼: * * public class Persion { * private String name; * private Integer age; * * @ConstructorProperties(value = {"coolblog", "20"}) * public Persion(String name, Integer age) { * this.name = name; * this.age = age; * } * } */ String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length); if (paramNames == null) { ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); if (pnd != null) { /* * 獲取構造方法參數名稱列表,比如有這樣一個構造方法: * public Person(String name, int age, String sex) * * 調用 getParameterNames 方法返回 paramNames = [name, age, sex] */ paramNames = pnd.getParameterNames(candidate); } } /* * 創建參數值列表,返回 argsHolder 會包含進行類型轉換后的參數值,比如下 * 面的配置: * * * * * Person 的成員變量 age 是 Integer 類型的,但由于在 Spring 配置中 * 只能配成 String 類型,所以這里要進行類型轉換。 */ argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, getUserDeclaredConstructor(candidate), autowiring); } catch (UnsatisfiedDependencyException ex) { if (this.beanFactory.logger.isTraceEnabled()) { this.beanFactory.logger.trace( "Ignoring constructor [" + candidate + "] of bean "" + beanName + "": " + ex); } if (causes == null) { causes = new LinkedList* * * (); } causes.add(ex); continue; } } else { if (paramTypes.length != explicitArgs.length) { continue; } argsHolder = new ArgumentsHolder(explicitArgs); } /* * 計算參數值(argsHolder.arguments)每個參數類型與構造方法參數列表 * (paramTypes)中參數的類型差異量,差異量越大表明參數類型差異越大。參數類型差異 * 越大,表明當前構造方法并不是一個最合適的候選項。引入差異量(typeDiffWeight) * 變量目的:是將候選構造方法的參數列表類型與參數值列表類型的差異進行量化,通過量化 * 后的數值篩選出最合適的構造方法。 * * 講完差異量,再來說說 mbd.isLenientConstructorResolution() 條件。 * 官方的解釋是:返回構造方法的解析模式,有寬松模式(lenient mode)和嚴格模式 * (strict mode)兩種類型可選。具體的細節沒去研究,就不多說了。 */ int typeDiffWeight = (mbd.isLenientConstructorResolution() ? argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes)); if (typeDiffWeight < minTypeDiffWeight) { constructorToUse = candidate; argsHolderToUse = argsHolder; argsToUse = argsHolder.arguments; minTypeDiffWeight = typeDiffWeight; ambiguousConstructors = null; } /* * 如果兩個構造方法與參數值類型列表之間的差異量一致,那么這兩個方法都可以作為 * 候選項,這個時候就出現歧義了,這里先把有歧義的構造方法放入 * ambiguousConstructors 集合中 */ else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) { if (ambiguousConstructors == null) { ambiguousConstructors = new LinkedHashSet >(); ambiguousConstructors.add(constructorToUse); } ambiguousConstructors.add(candidate); } } // 若上面未能篩選出合適的構造方法,這里將拋出 BeanCreationException 異常 if (constructorToUse == null) { if (causes != null) { UnsatisfiedDependencyException ex = causes.removeLast(); for (Exception cause : causes) { this.beanFactory.onSuppressedException(cause); } throw ex; } throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Could not resolve matching constructor " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)"); } /* * 如果 constructorToUse != null,且 ambiguousConstructors 也不為空,表明解析 * 出了多個的合適的構造方法,此時就出現歧義了。Spring 不會擅自決定使用哪個構造方法, * 所以拋出異常。 */ else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Ambiguous constructor matches found in bean "" + beanName + "" " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " + ambiguousConstructors); } if (explicitArgs == null) { /* * 緩存相關信息,比如: * 1. 已解析出的構造方法對象 resolvedConstructorOrFactoryMethod * 2. 構造方法參數列表是否已解析標志 constructorArgumentsResolved * 3. 參數值列表 resolvedConstructorArguments 或 preparedConstructorArguments * * 這些信息可用在其他地方,用于進行快捷判斷 */ argsHolderToUse.storeCache(mbd, constructorToUse); } } try { Object beanInstance; if (System.getSecurityManager() != null) { final Constructor> ctorToUse = constructorToUse; final Object[] argumentsToUse = argsToUse; beanInstance = AccessController.doPrivileged(new PrivilegedAction
上面的方法邏輯比較復雜,做了不少事情,該方法的核心邏輯是根據參數值類型篩選合適的構造方法。解析出合適的構造方法后,剩下的工作就是構建 bean 對象了,這個工作交給了實例化策略去做。下面羅列一下這個方法的工作流程吧:
創建 BeanWrapperImpl 對象
解析構造方法參數,并算出 minNrOfArgs
獲取構造方法列表,并排序
遍歷排序好的構造方法列表,篩選合適的構造方法
獲取構造方法參數列表中每個參數的名稱
再次解析參數,此次解析會將
計算構造方法參數列表與參數值列表之間的類型差異量,以篩選出更為合適的構造方法
緩存已篩選出的構造方法以及參數值列表,若再次創建 bean 實例時,可直接使用,無需再次進行篩選
使用初始化策略創建 bean 對象
將 bean 對象放入 BeanWrapperImpl 對象中,并返回該對象
由上面的流程可以看得出,通過構造方法自動注入的方式構造 bean 對象的過程還是很復雜的。為了看懂這個流程,我進行了多次調試,算是勉強弄懂大致邏輯。由于時間有限,我并未能詳細分析 autowireConstructor 方法及其所調用的一些方法,比如 resolveConstructorArguments、 autowireConstructor 等。關于這些方法,這里只寫了個大概,有興趣的朋友自己去探索吧。
2.3 通過默認構造方法創建 bean 對象看完了上面冗長的邏輯,本節來看點輕松的吧 - 通過默認構造方法創建 bean 對象。如下:
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) { try { Object beanInstance; final BeanFactory parent = this; // if 條件分支里的一大坨是 Java 安全相關的代碼,可以忽略,直接看 else 分支 if (System.getSecurityManager() != null) { beanInstance = AccessController.doPrivileged(new PrivilegedAction
上面就是通過默認構造方法創建 bean 對象的過程,比較簡單,就不多說了。最后我們再來看看簡單看看通過無參構造方法剛創建 bean 對象的代碼(通過 CGLIB 創建 bean 對象的方式就不看了)是怎樣的,如下:
public staticT instantiateClass(Constructor ctor, Object... args) throws BeanInstantiationException { Assert.notNull(ctor, "Constructor must not be null"); try { // 設置構造方法為可訪問 ReflectionUtils.makeAccessible(ctor); // 通過反射創建 bean 實例,這里的 args 是一個沒有元素的空數組 return ctor.newInstance(args); } catch (InstantiationException ex) { throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex); } catch (IllegalAccessException ex) { throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex); } catch (IllegalArgumentException ex) { throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex); } catch (InvocationTargetException ex) { throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException()); } }
到這里,終于看到了創建 bean 對象的代碼了。在經歷層層調用后,我們總算是追到了調用棧的最深處??吹竭@里,大家可以休息一下了,本文也差不多要結束了。好了,最后再容我多啰嗦一會,往下看。
3.寫在最后寫到這里,我也算是松了一口氣,終于快寫完了。這篇文章寫起來感覺挺不容易的,原因是 createBeanInstance 及其調用的方法是在太多了,而且很多方法邏輯還是比較復雜的,尤其是 autowireConstructor 中調用的一些方法。autowireConstructor 中調用的方法我基本上都看了一遍,但并非全部都弄懂了,有些方法只是知道個大概。所以,這篇文章寫的我挺糾結的,生怕有些地方分析的不對。由于我后續還有很多東西要看,以至于我暫時沒法抽出大量的時間去詳細閱讀 Spring 的源碼。所以如果上面的分析有不對的地方,歡迎指正,我會虛心聽之。如果這些不對的地方給你造成了困擾,實在很抱歉,抱歉。
好了,本篇文章先到這里。謝謝閱讀!
參考《Spring 源碼深度解析》- 郝佳
附錄:Spring 源碼分析文章列表 Ⅰ. IOC更新時間 | 標題 |
---|---|
2018-05-30 | Spring IOC 容器源碼分析系列文章導讀 |
2018-06-01 | Spring IOC 容器源碼分析 - 獲取單例 bean |
2018-06-04 | Spring IOC 容器源碼分析 - 創建單例 bean 的過程 |
2018-06-06 | Spring IOC 容器源碼分析 - 創建原始 bean 對象 |
2018-06-08 | Spring IOC 容器源碼分析 - 循環依賴的解決辦法 |
2018-06-11 | Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對象 |
2018-06-11 | Spring IOC 容器源碼分析 - 余下的初始化工作 |
更新時間 | 標題 |
---|---|
2018-06-17 | Spring AOP 源碼分析系列文章導讀 |
2018-06-20 | Spring AOP 源碼分析 - 篩選合適的通知器 |
2018-06-20 | Spring AOP 源碼分析 - 創建代理對象 |
2018-06-22 | Spring AOP 源碼分析 - 攔截器鏈的執行過程 |
更新時間 | 標題 |
---|---|
2018-06-29 | Spring MVC 原理探秘 - 一個請求的旅行過程 |
2018-06-30 | Spring MVC 原理探秘 - 容器的創建過程 |
本文在知識共享許可協議 4.0 下發布,轉載需在明顯位置處注明出處
作者:coolblog.xyz
本文同步發布在我的個人博客:http://www.coolblog.xyz
本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69664.html
摘要:實例化時,發現又依賴于。一些緩存的介紹在進行源碼分析前,我們先來看一組緩存的定義??墒强赐暝创a后,我們似乎仍然不知道這些源碼是如何解決循環依賴問題的。 1. 簡介 本文,我們來看一下 Spring 是如何解決循環依賴問題的。在本篇文章中,我會首先向大家介紹一下什么是循環依賴。然后,進入源碼分析階段。為了更好的說明 Spring 解決循環依賴的辦法,我將會從獲取 bean 的方法getB...
摘要:簡介本篇文章是容器源碼分析系列文章的最后一篇文章,本篇文章所分析的對象是方法,該方法用于對已完成屬性填充的做最后的初始化工作。后置處理器是拓展點之一,通過實現后置處理器接口,我們就可以插手的初始化過程。 1. 簡介 本篇文章是Spring IOC 容器源碼分析系列文章的最后一篇文章,本篇文章所分析的對象是 initializeBean 方法,該方法用于對已完成屬性填充的 bean 做最...
摘要:本文是容器源碼分析系列文章的第一篇文章,將會著重介紹的一些使用方法和特性,為后續的源碼分析文章做鋪墊。我們可以通過這兩個別名獲取到這個實例,比如下面的測試代碼測試結果如下本小節,我們來了解一下這個特性。 1. 簡介 Spring 是一個輕量級的企業級應用開發框架,于 2004 年由 Rod Johnson 發布了 1.0 版本。經過十幾年的迭代,現在的 Spring 框架已經非常成熟了...
摘要:源碼分析源碼一覽本節,我們先來看一下填充屬性的方法,即。所有的屬性值是在方法中統一被注入到對象中的。檢測是否存在與相關的或。這樣可以在很大程度上降低源碼分析的難度。若候選項是非類型,則表明已經完成了實例化,此時直接返回即可。 1. 簡介 本篇文章,我們來一起了解一下 Spring 是如何將配置文件中的屬性值填充到 bean 對象中的。我在前面幾篇文章中介紹過 Spring 創建 bea...
摘要:關于創建實例的過程,我將會分幾篇文章進行分析。源碼分析創建實例的入口在正式分析方法前,我們先來看看方法是在哪里被調用的。時,表明方法不存在,此時拋出異常。該變量用于表示是否提前暴露單例,用于解決循環依賴。 1. 簡介 在上一篇文章中,我比較詳細的分析了獲取 bean 的方法,也就是getBean(String)的實現邏輯。對于已實例化好的單例 bean,getBean(String) ...
閱讀 3513·2021-11-17 17:01
閱讀 3918·2021-11-08 13:12
閱讀 2477·2021-10-08 10:04
閱讀 687·2021-09-29 09:35
閱讀 1418·2021-09-26 10:12
閱讀 2020·2021-09-07 09:58
閱讀 1953·2019-08-30 15:55
閱讀 2134·2019-08-30 13:14