javascript
Spring Ioc源码分析 之 Bean的加载(4):实例化Bean(createBeanInstance()方法)
實例化 Bean
在doCreateBean()代碼 <2> 處,有一行代碼instanceWrapper = createBeanInstance(beanName, mbd, args); 我們追蹤進去看一下:
//AbstractAutowireCapableBeanFactory.java//創建Bean的實例對象protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// Make sure bean class is actually resolved at this point.//解析beanName 為 classClass<?> beanClass = resolveBeanClass(mbd, beanName);//檢查確認Bean是可實例化的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());}//如果存在 Supplier 回調,則使用給定的回調方法初始化策略Supplier<?> instanceSupplier = mbd.getInstanceSupplier();if (instanceSupplier != null) {return obtainFromSupplier(instanceSupplier, beanName);}//使用 FactoryBean 的 factory-method 來創建,支持靜態工廠和實例工廠if (mbd.getFactoryMethodName() != null) {//調用工廠方法實例化return instantiateUsingFactoryMethod(beanName, mbd, args);}// Shortcut when re-creating the same 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 {//使用默認的無參構造方法實例化return instantiateBean(beanName, mbd);}}// Need to determine the constructor...//需要根據參數解析構造函數Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);if (ctors != null ||mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {//使用容器的自動裝配特性,調用匹配的構造方法實例化return autowireConstructor(beanName, mbd, ctors, args);}// No special handling: simply use no-arg constructor.//使用默認的無參構造方法實例化return instantiateBean(beanName, mbd);}這段代碼中,Spring把Bean的實例話分為了4種方式:
1.Supplier 回調
2.工廠方法初始化
3.構造函數自動注入初始化
4.默認無參構造方法初始化
一、Supplier 回調
如果存在 Supplier 回調,則調用 obtainFromSupplier(Supplier<?> instanceSupplier, String beanName) 方法,進行初始化。Supplier是一個接口,定義如下:
public interface Supplier<T> {T get(); }這個接口有什么作用?用于指定創建 bean 的回調。如果我們設置了這樣的回調,那么其他的構造器或者工廠方法都會沒有用
設置的地方在BeanDefinition的構造函數中,如:
二、工廠方法初始化
如果存在工廠方法,則使用工廠方法進行初始化。這部分代碼非常長,很復雜,這里就不詳細說了。
三、構造函數自動注入初始化
首先判斷緩存,如果緩存中存在(resolved==true),即已經解析過了,則直接使用已經解析了的。否則,先解析構造函數,然后通過構造函數自動注入初始化。
- 3.1、autowireConstructor()
autowireConstructor() 這個初始化方法,我們可以簡單理解為通過帶有參數的構造方法,來初始化 Bean 對象。帶有參數的實例化過程相當復雜,因為存在這不確定性,所以在判斷對應參數上做了大量工作。
代碼段如下:
代碼很長,但不要慌,我們來一步步分析:
<1>處,判斷有無顯式指定構造參數 <2>處,沒有顯式指定參數,則從緩存中獲取 <3>處,緩存不存在,解析構造函數參數 <4>處,獲取構造參數個數 <5>處,獲取所有構造方法 <6>處,對所有構造方法排序 <7>處,遍歷所有構造方法 <8>處,通過參數校驗構造方法 <9>處,創建參數持有者 ArgumentsHolder <10>處,篩選出符合的構造方法 <11>處,將解析的構造函數、參數 加入緩存 <12>處,實例化Bean對象- 3.1.1、判斷有無顯式指定構造參數
explicitArgs:外部傳入的指定構造參數
argsToUse:要使用的構造參數
explicitArgs:是指外部傳入的指定構造參數,例如xxxBeanFactory.getBean(“teacher”, “李華”,3),(李華和3)就是傳入的指定參數。
argsToUse 是我們實例化時要使用的構造參數,這里判斷如果explicitArgs不為null的化,就把explicitArgs賦值給 argsToUse。
- 3.1.2、沒有顯式指定參數,則從緩存中獲取
首先從緩存中mbd.resolvedConstructorOrFactoryMethod獲取構造方法,如果緩存中存在構造方法和參數,就解析構造參數。
因為緩存中的構造參數不一定是最終值,如給定方法的構造函數 A(int ,int ),則通過此方法后就會把配置文件中的(“1”,“1”)轉換為 (1,1)
- 3.1.3、緩存不存在,解析構造函數參數
如果緩存不存在,則需要解析構造函數參數,以確定使用哪一個構造函數來進行實例化
- 3.1.4、獲取構造參數個數
如果explicitArgs不為null,則直接獲取。
為null:需要解析保存在 BeanDefinition 構造函數中指定的參數并獲取能解析到的參數個數
- 3.1.5、獲取所有構造方法
先嘗試獲取指定的構造方法,如果沒有,則利用反射獲取所有構造方法
- 3.1.6、對所有構造方法排序
排序的主要目的,是為了能夠更加方便的找到最匹配的構造方法,因為構造方法的確認是根據參數個數確認的。排序的規則是:先按照 public / 非 public 構造方法升序,再按照構造參數數量降序。
- 3.1.7、遍歷所有構造方法
遍歷所有構造方法,篩選出最匹配的一個
- 3.1.8、通過參數校驗構造方法
這段代碼也不復雜,第一個if是break分支,滿足條件就跳出for循環,到這里就意為著找到了最匹配的構造方法。
EX: 假設現在有一組構造方法按照上面的排序規則進行排序,排序結果如下:
由于是按降序排序的,所以會先去匹配構造方法1,發現 argsToUse.length > paramTypes.length
第二個if是快速判斷當前構造方法是否符合我們的要求。
paramTypes:當前構造方法的參數個數
minNrOfArgs:我們要求的構造方法的參數個數 如果當前的構造參數個數小于我們要求的個數,說明當前構造方法不符合我們的要求,直接 continue
- 3.1.9、創建參數持有者 ArgumentsHolder
這里主要有兩個邏輯:
resolvedValues != null:即沒有顯示指定構造參數
resolvedValues == null:即顯示指定了構造參數
- 第一個分支:
先通過@ConstructorProperties注解獲取構造參數名稱,如果獲取不到,再通過ParameterNameDiscoverer獲取,最后創建 ArgumentsHolder
- 第二個分支:
直接使用顯示傳入的構造參數 explicitArgs 來 new 一個ArgumentsHolder
將參數包裝成 ArgumentsHolder 對象。該對象用于保存參數,我們稱之為參數持有者。在這個過程中再次解析構造參數,進行類型轉換,如把配置文件中的string轉換成需要的int。
當將對象包裝成 ArgumentsHolder 對象后,我們就可以通過它來進行構造函數匹配。匹配分為嚴格模式和寬松模式:
嚴格模式:解析構造函數時,必須所有參數都需要匹配,否則拋出異常。
寬松模式:從模棱兩可的構造方法中,選擇最接近的。 判斷的依據是根據BeanDefinition 的 isLenientConstructorResolution 屬性(該參數是我們在構造 AbstractBeanDefinition 對象是傳遞的)來獲取類型差異權重(typeDiffWeight) 的。
- 3.1.10、篩選出符合的構造方法
先通過計算得出當前構造方法的差異值typeDiffWeight,每次和分數最小的去比較,篩選出差異值最小的,最終比較出一個最匹配的構造方法。
差異值大于最小差異值的,加入到候選集合ambiguousConstructors,我稱之為模棱兩可的構造方法,該集合在《寬松模式》下使用。
至此,所有構造方法都遍歷完畢。如果仍沒有篩選出構造方法,拋出異常。
如果模棱兩可的構造方法不為空,但模式為 嚴格模式,則拋異常。
- 3.1.11、將解析的構造函數、參數 加入緩存
繼續追蹤:
// ArgumentsHolder.javapublic final Object rawArguments[];public final Object arguments[];public final Object preparedArguments[];public void storeCache(RootBeanDefinition mbd, Executable constructorOrFactoryMethod) {synchronized (mbd.constructorArgumentLock) {mbd.resolvedConstructorOrFactoryMethod = constructorOrFactoryMethod;mbd.constructorArgumentsResolved = true;if (this.resolveNecessary) {mbd.preparedConstructorArguments = this.preparedArguments;}else {mbd.resolvedConstructorArguments = this.arguments;}}}相信大家看到這里應該對resolvedConstructorOrFactoryMethod 和 resolvedConstructorArguments等這幾個參數很熟悉。
正如你所想,在前面判斷緩存中是否存在的時候,就是通過這幾個參數來判斷的。
- 3.1.12、實例化Bean對象
strategy.instantiate 這部分代碼還是挺多的,下回分析。 - 1.3.2、圖解流程
因為這段代碼還是挺復雜的,所以我畫了一個(explicitArgs=null)的分支流程圖,便于理解
- 1.3、默認無參構造方法初始化
經過有參構造方法初始化源碼的摧殘之后,再來看無參的源碼,會發現簡單多了。
繼續追蹤:
//使用默認的無參構造方法實例化Bean對象protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {try {Object beanInstance;final BeanFactory parent = this;//獲取系統的安全管理接口,JDK標準的安全管理APIif (System.getSecurityManager() != null) {//這里是一個匿名內置類,根據實例化策略創建實例對象beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->getInstantiationStrategy().instantiate(mbd, beanName, parent),getAccessControlContext());}else {//將實例化的對象封裝起來beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);}BeanWrapper bw = new BeanWrapperImpl(beanInstance);initBeanWrapper(bw);return bw;}catch (Throwable ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);}}看過有參構造方法初始化的源碼之后,再看看無參的,發現代碼真的簡單太多了,沒有復雜的確定構造參數、構造方法的邏輯。
instantiate(mbd, beanName, parent)
//SimpleInstantiationStrategy.java//使用初始化策略實例化Bean對象@Overridepublic Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {// Don't override the class with CGLIB if no overrides.// 沒有覆蓋,直接使用反射實例化即可if (!bd.hasMethodOverrides()) {Constructor<?> constructorToUse;synchronized (bd.constructorArgumentLock) {//從緩存中獲取對象的構造方法或工廠方法constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;//緩存沒有if (constructorToUse == null) {//使用JDK的反射機制,判斷要實例化的Bean是否是接口final Class<?> clazz = bd.getBeanClass();if (clazz.isInterface()) {throw new BeanInstantiationException(clazz, "Specified class is an interface");}try {if (System.getSecurityManager() != null) {//這里是一個匿名內置類,使用反射機制獲取Bean的構造方法constructorToUse = AccessController.doPrivileged((PrivilegedExceptionAction<Constructor<?>>) () -> clazz.getDeclaredConstructor());}else {constructorToUse = clazz.getDeclaredConstructor();}bd.resolvedConstructorOrFactoryMethod = constructorToUse;}catch (Throwable ex) {throw new BeanInstantiationException(clazz, "No default constructor found", ex);}}}//使用BeanUtils實例化,通過反射機制調用”構造方法.newInstance(arg)”來進行實例化return BeanUtils.instantiateClass(constructorToUse);}else {// Must generate CGLIB subclass.//有方法覆蓋,使用CGLIB來實例化對象//方法覆蓋,在調用目標方法的時候,對調用過程進行攔截,調用實現增強功能的攔截器,返回原來實例的代理//所以要用cglib動態代理return instantiateWithMethodInjection(bd, beanName, owner);}}很簡單的幾個步驟:
1.判斷有無方法覆蓋
2.嘗試從緩存中獲取構造方法
3.校驗bean是否為interface
4.利用反射獲取默認構造方法
5.利用BeanUtils實例化
BeanUtils.instantiateClass(constructorToUse)
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {Assert.notNull(ctor, "Constructor must not be null");try {// 設置構造方法,可訪問ReflectionUtils.makeAccessible(ctor);// 使用構造方法,創建對象 newInstancereturn (KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?KotlinDelegate.instantiateClass(ctor, args) : 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());}}先設置強吻訪問,然后newInstance()創建對象。
總結:對于 createBeanInstance() 方法而言,他就是選擇合適實例化策略來為 bean 創建實例對象,具體的策略有:
1.Supplier 回調方式2.工廠方法初始化3.構造函數自動注入初始化4.默認構造函數注入。其中,工廠方法初始化和構造函數自動注入初始化兩種方式最為復雜,主要是因為構造函數和構造參數的不確定性,Spring 需要花大量的精力來確定構造函數和構造參數,如果確定了則好辦,直接選擇實例化策略即可。
當然,在實例化的時候會根據是否有需要覆蓋或者動態替換掉的方法,因為存在覆蓋或者織入的話需要創建動態代理將方法織入,這個時候就只能選擇 CGLIB 的方式來實例化,否則直接利用反射的方式即可,方便快捷。
最后:到這里實例化Bean的代碼就分析完了。
文章轉自:https://cloud.tencent.com/developer/article/1508894
總結
以上是生活随笔為你收集整理的Spring Ioc源码分析 之 Bean的加载(4):实例化Bean(createBeanInstance()方法)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2021年京东小魔方年中新品消费趋势报告
- 下一篇: gradle idea java ssm