Spring使用有參構造器創建對象autowireConstructor方法


前提

你得知道Spring創建Bean的基本流程,我們這里解釋的是Spring創建Bean時使用有參構造器去創建Bean的源碼解析。

autowireConstructor方法

這個方法里面就是拿到類的構造器,然后選取到最合適的,然后進而通過構造器來進行初始化。

方法總覽

沒想到很好的表達方式,就把注釋補充完整,可以直接看注釋

/**
* 根據給定的構造器列表(如果給的構造器參數是空的,那么則會去獲取該類的構造器數組),Spring去找出一個最適合的構造器,然后通過這個構造器去實例化對象出來.
* 	beanName:	Bean的名稱
*   mbd:		該bean的BeanDefinition
*   chosenCtors: 該類的構造器數組
*   explicitArgs:查看調用鏈你會發現,這個方法時通過getBean方法過來的,那么既然是getBea過來的,那么在我們getBean的時候除了傳入beanName/beanClass以外,還可以傳入其他參數,如果傳入了其他參數,那么Spring
* 	            		則認為這些參數是構造器初始化對象時的構造方法參數列表,而這個其他參數就是此時的explicitArgs.
* 	            		getBean帶參數的參考:org.springframework.beans.factory.BeanFactory#getBean(java.lang.Class, java.lang.Object...)
*/
public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
			@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
		/**
		 * 一句廢話:
		 * 	BeanWrapperImpl是BeanWrapper的實現類
		 *  並且這個類用來存儲Bean的名稱,class,實例對象等.
		 * */
		BeanWrapperImpl bw = new BeanWrapperImpl();
		/**
		 * 初始化這個BeanWrapper
		 * */
		this.beanFactory.initBeanWrapper(bw);
		/**
		 * constructorToUse是我們實際上使用的構造器,因為傳進來的構造器一看見就知道是個數組,那么Spring需要篩選出來最適合的構造器,那么篩選出來最適合的構造器就會賦值給這里的constructorToUse
		 * */
		Constructor<?> constructorToUse = null;
		/**
		 * argsHolderToUse用來存儲用到的構造器的參數,下面的argsToUse的值也是從這個argsHolderToUse中取出來的
		 */
		ArgumentsHolder argsHolderToUse = null;
		/**
		 * 構造方法中使用到的參數列表實際的值
		 * */
		Object[] argsToUse = null;
		/**
		 * 總結一下這個if和else:
		 * 	1、判斷是否傳入構造參數值列表,如果傳入則賦值
		 * 	2、沒有傳入則從緩存中去取
		 * */
		/**
		 * 如果我們getBean的時候傳入了參數,那么Spring就認為我們希望按照指定的構造參數列表去尋找構造器並實例化對象.
		 * 那么這里如果不為空則實際上需要使用的構造方法參數列表值就已經確定了
		 * */
		if (explicitArgs != null) {
			argsToUse = explicitArgs;
		}
		else {
			/**
			 * 這里干的事情很簡單,就是如果這個bean是原型的,那么說明此方法肯定進入過,那么也肯定找到過合適的構造方法和構造參數值列表,在找到
			 * 合適的構造方法和構造參數值列表后會加入到緩存里面去,那么此處如果不是第一次進入的話,那么緩存里面已經有了不用再次去獲取.
			 * 此處做的工作就是從緩存中去獲取已經找到過並存進來的構造方法和構造參數值列表.
			 * 這個加入緩存的操作在下面的代碼里面才有,此處不理解可以看到下面的代碼后再去理解.
			 * 還有注意,只有當我們參數中explicitArgs為空的時候,構造器才會被緩存,同樣也是下面解釋.
			 * */
			Object[] argsToResolve = null;
			synchronized (mbd.constructorArgumentLock) {
				constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
				if (constructorToUse != null && mbd.constructorArgumentsResolved) {
					// Found a cached constructor...
					argsToUse = mbd.resolvedConstructorArguments;
					if (argsToUse == null) {
						argsToResolve = mbd.preparedConstructorArguments;
					}
				}
			}
			/* 如果緩存中存在則去拿出來賦值.其實都沒必要在這里加注釋 */
			if (argsToResolve != null) {
				argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);
			}
		}
		/**
		 * 這里判斷constructorToUse是否為空是因為上面有可能從緩存里面已經拿到了,如果拿到了則不需要進if里面去尋找,直接去調用創建實例化操作了.
		 * 這里重要的是if里面的代碼.
		 * */
		if (constructorToUse == null) {
			// Need to resolve the constructor.
			boolean autowiring = (chosenCtors != null ||
					mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
			/**
			 * 構造器使用的參數
			 * */
			ConstructorArgumentValues resolvedValues = null;
			/**
			 * 最小參數個數,此值需要用來在循環尋找構造器時使用.
			 * 如果當當前循環到的構造器參數值個數小於這個最小值的話,那么說明就是不合適的,沒必要繼續下去.
			 * */
			int minNrOfArgs;
			/**
			* 如果我們getBean的地方傳入構造參數值列表,那么則最小參數個數就是我們傳入的列表長度
			* */
			if (explicitArgs != null) {
				minNrOfArgs = explicitArgs.length;
			}
			else {
				/**
				 * 如果我們沒有傳入構造器參數值列表,那么則去解析看有沒有配置構造器參數列表,例如如下配置:
				 * <bean class="com.dh.aop.package1.Demo2" id="demo2">
				 * 		<constructor-arg index="0" value="111"></constructor-arg>
				 * 		<constructor-arg index="1" value="222"></constructor-arg>
				 * 	</bean>
				 * 	這個時候,minNrOfArgs的值就是2
				 * 	如果我們沒有配置構造器參數的話,這個minNrOfArgs的值就是0
				 * */
				ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
				resolvedValues = new ConstructorArgumentValues();
				minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
			}

			// Take specified constructors, if any.
			/**
			 * 將該方法的參數構造器列表賦值給candidates
			 * */
			Constructor<?>[] candidates = chosenCtors;
			/**
			 * 如果傳入的構造器列表為空,則通過class對象去拿
			 * 如果bd中設置了允許訪問非public的構造器,那么則獲取所有的構造器,否則獲取public的構造器.
			 * 注意這里isNonPublicAccessAllowed的默認值為true.
			 * 如果獲取構造器的時候出錯當然就要拋異常.
			 * */
			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);
				}
			}
			/**
			 * 對構造器進行排序:
			 * 	public的大於其他的權限
			 * 	如果都是public的,那么參數越多越靠前.
			 * 	可以看這個sort方法里面可以看到的
			 * */
			AutowireUtils.sortConstructors(candidates);
			/**
			 * 差異變量
			 * */
			int minTypeDiffWeight = Integer.MAX_VALUE;
			/**
			 * 有歧義的構造器,就是參數數量一致的,這種情況下的構造器就被列為有歧義的.
			 * 正常情況下,如果出現有歧義的構造器,那么就使用第一個,這取決於spring設置的寬松模式.
			 * 默認為寬松,如此的話就默認使用第一個構造器使用
			 * 如果設置為嚴格,則會報錯
			 * 設置寬松/嚴格模式標志:beanDefinition.setLenientConstructorResolution
			 * */
			Set<Constructor<?>> ambiguousConstructors = null;
			LinkedList<UnsatisfiedDependencyException> causes = null;
			/**
			 * 下面就是循環的拿構造器去校驗判斷選取一個合適的構造器了.在此之前我們總結一下上述代碼做的事情.
			 * 1、定義constructorToUse、argsHolderToUse、argsToUse,這些分別用來存后面實際上需要使用的構造器、構造器參數、值等
			 * 2、如果getBean調用的時候傳入了構造器參數,那么argsToUse的值就被賦值為傳入的構造器參數,否則就嘗試從緩存里面去拿constructorToUse和argsToUse
			 * 		這個緩存就是當bean不是原型的時候實例化時找到的合適的構造器等參數,當然如果是第一次進來,或者bean是單例的,那么此緩存中肯定沒有這個bean相關的構造器數據
			 * 3、如果緩存里面有,則直接實例化bean后放到wrapper中並return,如果不存在則需要再次進行一些操作
			 * 4、在不存在時,首先定義resolvedValues,這個是后續循環里面需要使用到的構造器使用的參數列表,定義minNrOfArgs,這個是最小參數個數,首先如果getBean傳入了構造器參數
			 * 那么此值就是傳入構造參數的長度,否則就嘗試看我們有沒有配置使用某個構造器,如果都沒有,那么這個值就是0了,這個變量用來后面在循環構造器的時候篩選用的
			 * 5、然后定義candidates變量,然后將chosenCtors(是前面傳入的構造器列表)賦值過去,如果它為空,那么則需要去通過class去拿構造器,拿的時候判斷了一手
			 * 去拿BeanDefinition中的isNonPublicAccessAllowed,這個isNonPublicAccessAllowed意思為是否允許訪問非public的構造器,如果為true,則去獲取所有的構造器,否則只獲取public的
			 * 6、然后對所有的構造器進行排序,規則為public>其他權限,參數個數多的>參數個數少的,至於為什么排序這個可能是spring認為參數越多的越科學吧
			 * 7、差異變量,這個看循環里面的代碼才能理解
			 * 8、定義ambiguousConstructors為有歧義的構造器,意思就是如果兩個構造器參數一致,那Spring就不知道該去用哪個,這時這兩個構造器就被放入ambiguousConstructors集合中,他們兩個就是有歧義的構造器
			 * ================================================================================
			 * 下面循環里面需要搞清楚的就是它具體是如何選取到合適的構造器來使用
			 * */
			for (Constructor<?> candidate : candidates) {
				/**
				 * 拿到當前構造方法的參數class數組
				 * */
				Class<?>[] paramTypes = candidate.getParameterTypes();
				/**
				 * 前面說了[constructorToUse]這個變量是當前確定使用的構造器,如果它不為空,那么說明我們已經確定了使用哪個構造器,那么就沒必要繼續下去了.
				 * 但[argsToUse.length > paramTypes.length]這個就比較難理解.
				 * 注意每次循環以后argsToUse的值就會改變為那次循環的構造器的參數,如果當前拿到的argsToUse參數列表的長度大於當前這個構造器的長度,那么說明上一次拿到的這個argsToUse比當前的這個更合適(上面也說過,Spring認為參數越多的越科學)
				 * 這里可以注意一下前面sort排序的時候,構造參數個數越多的越靠前,所以這里敢於用長度判斷后直接break,因為如果上一次循環的構造器參數列表為2個,那么這一次(也就是下一次)的構造參數列表肯定不會比2大,那么說明對於參數個數而言,上一次的參數個數肯定不會比這一次少,那么肯定就更合適了唄
				 */
				if (constructorToUse != null && argsToUse.length > paramTypes.length) {
					// Already found greedy constructor that can be satisfied ->
					// do not look any further, there are only less greedy constructors left.
					break;
				}
				/**
				 * 如果當前構造器的參數數量比最小參數列表數量小的時候,那么跳過這個構造器.
				 * minNrOfArgs的值有兩個地方賦值了:
				 * 	1、如果我們getBean時傳入了其他參數,那么其他參數的個數就是minNrOfArgs的值
				 * 	2、如果我們getBean沒有傳參數,那么minNrOfArgs的值就是我們配置讓Spring指定使用某些參數的構造器,那么我們配置的參數列表數量也就是當前的minNrOfArgs
				 * 	3、如果上述的情況都不存在,那么minNrOfArgs就是0了,大多數時候都是這種情況,如果都沒配置,那么就得Spring自己慢慢而不存在此處的篩選了.
				 * 所以總結來說此處就是做了一個根據我們自己定義的來篩選的操作
				 * */
				if (paramTypes.length < minNrOfArgs) {
					continue;
				}
				/**
				 * 存儲構造器需要的參數
				 * */
				ArgumentsHolder argsHolder;
				/**
				 * 此處resolvedValues不為空則說明getBean時傳入的參數explicitArgs為空的,
				 * 因為上面的代碼是如果explicitArgs不為空則不對resolvedValues賦值,否則就對resolvedValues賦值
				 * 此處先看else的代碼,會更清晰.
				 * 如果傳入的參數為空,那么則會去拿參數了
				 * */
				if (resolvedValues != null) {
					try {
						/**
						 * 去拿到參數列表的名稱,這里ConstructorPropertiesChecker.evaluate是這樣做的:
						 * 如果構造方法上加入了ConstructorProperties注解,那么說明我們參數名稱數組,如果沒有這個注解,那么次數paramNames為空的
						 * */
						String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length);
						/** 這里為空則代表我們沒有通過注解去自定義參數名稱,則通過ParameterNameDiscoverer去解析拿到構造器的參數名稱列表 */
						if (paramNames == null) {
							ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
							if (pnd != null) {
								/** 解析拿到參數名稱列表 */
								paramNames = pnd.getParameterNames(candidate);
							}
						}
						/**
						 * 此處會去獲取這些參數名稱的參數值,如果是自動注入的就會通過getBean獲取,當前這種構造器注入的情況如果循環依賴則會報錯的.
						 * 這里我們只需要知道,此處將構造器需要的參數值拿出來后並封裝到了argsHolder中去.
						 * 當然如果你構造器里面給個Integer的參數,那肯定是會報錯的,因為這里面會去Spring容器中拿這個Integer,結果呢,肯定是NoSuchBeanDefinitionException了
						 * 其余這里不用太過於細究,有興趣可以詳細看.
						 * */
						argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
								getUserDeclaredConstructor(candidate), autowiring);
					}
					catch (UnsatisfiedDependencyException ex) {
						if (logger.isTraceEnabled()) {
							logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
						}
						// Swallow and try next constructor.
						if (causes == null) {
							causes = new LinkedList<>();
						}
						causes.add(ex);
						continue;
					}
				}
				else {
					/**
					 * 到了這個else里面來,說明getBean調用的時候傳入了構造器參數,那么就說明我們希望按照指定的構造器去初始化Bean.
					 * 那么這里就需要判斷當前構造器的參數個數是否和我們希望的個數一樣,如果不是,那么就循環去找下一個構造器,
					 * 如果和我們希望的是一樣的,那么就將我們給的參數封裝到argsHolder里面去
					 * */
					// Explicit arguments given -> arguments length must match exactly.
					if (paramTypes.length != explicitArgs.length) {
						continue;
					}
					argsHolder = new ArgumentsHolder(explicitArgs);
				}
				/**
				 * 當到達這里的時候,至此我們拿到了:
				 * 	1、構造器
				 * 	2、構造器需要的參數和值
				 * 那么這里就去結算前面定義的那個差異值.
				 * 注意這里的:isLenientConstructorResolution意思是是否為寬松的模式,為true的時候是寬松,false的時候是嚴格,默認為true,這個東西前面已經說了.
				 * 這個差異值越小越那就說明越合適.
				 * 具體差異值如何計算出來的這個可以自行去看里面的代碼,argsHolder.getTypeDifferenceWeight(paramTypes)
				 * */
				int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
						argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
				// Choose this constructor if it represents the closest match.
				/**
				 * 如果本次計算到的差異值比上一次獲取到的差異值小,那么就需要做這幾件事:
				 * 	1、設置constructorToUse為當前的這個構造器
				 * 	2、設置參數和參數值
				 * 	3、給差異值賦值為當前計算出來的差異值
				 * 	4、清空有歧義的集合(因為此時我們已經得到了更合適的構造器,所以有歧義的構造器里面保存的構造器已經沒有存在的意義了)
				 * */
				if (typeDiffWeight < minTypeDiffWeight) {
					constructorToUse = candidate;
					argsHolderToUse = argsHolder;
					argsToUse = argsHolder.arguments;
					minTypeDiffWeight = typeDiffWeight;
					ambiguousConstructors = null;
				}
				/**
				 * 如果已經找到過一次構造器,並且當前的差異值和上一次的差異值一致的話,那么說明這兩個構造器是有歧義的,
				 * 那么就將這兩個構造器放到[有歧義的構造器]集合中去.
				 * */
				else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
					if (ambiguousConstructors == null) {
						ambiguousConstructors = new LinkedHashSet<>();
						ambiguousConstructors.add(constructorToUse);
					}
					ambiguousConstructors.add(candidate);
				}
			}
			/**
			 * 到達這里的時候,如果還沒有找到合適的構造器,那么則直接拋出異常,這個類沒法實例化
			 * */
			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)");
			}
			/**
			 * 如果存在歧義的構造器集合不為空,並且當前BeanDefinition為嚴格模式,那么則拋出異常,只有當BeanDefinition為寬松模式時,這種情況才不會拋異常
			 * */
			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);
			}
			/**
			 * 如果當前getBean沒有傳參數,那么則將當前的構造器和參數放到緩存里面去,可能Spring認為傳入參數的情況下說不准我們准備怎么做,所以干脆我們自己傳入參數的就不緩存了
			 * */
			if (explicitArgs == null) {
				argsHolderToUse.storeCache(mbd, constructorToUse);
			}
		}
		/**
		 * 以下沒什么好說的,new示例出來,存入beanWrapper中,return回去
		 * */
		try {
			final InstantiationStrategy strategy = beanFactory.getInstantiationStrategy();
			Object beanInstance;

			if (System.getSecurityManager() != null) {
				final Constructor<?> ctorToUse = constructorToUse;
				final Object[] argumentsToUse = argsToUse;
				beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
						strategy.instantiate(mbd, beanName, beanFactory, ctorToUse, argumentsToUse),
						beanFactory.getAccessControlContext());
			}
			else {
				beanInstance = strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
			}

			bw.setBeanInstance(beanInstance);
			return bw;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean instantiation via constructor failed", ex);
		}
	}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM