在Spring中,一個bean需要透過例項化來獲取一個物件,而例項化的過程涉及到構造方法的呼叫。本文將主要探討簡單的構造推斷和例項化過程,讓我們首先深入瞭解例項化的步驟。
例項化原始碼
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// Make sure bean class is actually resolved at this point.
Class<?> beanClass = resolveBeanClass(mbd, beanName);
.....
// BeanDefinition中新增了Supplier,則呼叫Supplier來得到物件
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName);
}
// @Bean對應的BeanDefinition
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// Shortcut when re-creating the same bean...
// 一個原型BeanDefinition,會多次來建立Bean,那麼就可以把該BeanDefinition所要使用的構造方法快取起來,避免每次都進行構造方法推斷
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
synchronized (mbd.constructorArgumentLock) {
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
// autowireNecessary表示有沒有必要要進行注入,比如當前BeanDefinition用的是無參構造方法,那麼autowireNecessary為false,否則為true,表示需要給構造方法引數注入值
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if (resolved) {
// 如果確定了當前BeanDefinition的構造方法,那麼看是否需要進行對構造方法進行引數的依賴注入(構造方法注入)
if (autowireNecessary) {
// 方法內會拿到快取好的構造方法的入參
return autowireConstructor(beanName, mbd, null, null);
}
else {
// 構造方法已經找到了,但是沒有引數,那就表示是無參,直接進行例項化
return instantiateBean(beanName, mbd);
}
}
// 如果沒有找過構造方法,那麼就開始找了
// Candidate constructors for autowiring?
// 提供一個擴充套件點,可以利用SmartInstantiationAwareBeanPostProcessor來控制用beanClass中的哪些構造方法
// 比如AutowiredAnnotationBeanPostProcessor會把加了@Autowired註解的構造方法找出來,具體看程式碼實現會更復雜一點
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 如果推斷出來了構造方法,則需要給構造方法賦值,也就是給構造方法引數賦值,也就是構造方法注入
// 如果沒有推斷出來構造方法,但是autowiremode為AUTOWIRE_CONSTRUCTOR,則也可能需要給構造方法賦值,因為不確定是用無參的還是有參的構造方法
// 如果透過BeanDefinition指定了構造方法引數值,那肯定就是要進行構造方法注入了
// 如果呼叫getBean的時候傳入了構造方法引數值,那肯定就是要進行構造方法注入了
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// Preferred constructors for default construction?
ctors = mbd.getPreferredConstructors();
if (ctors != null) {
return autowireConstructor(beanName, mbd, ctors, null);
}
// No special handling: simply use no-arg constructor.
// 不匹配以上情況,則直接使用無參構造方法
return instantiateBean(beanName, mbd);
}
在Spring框架中的AbstractAutowireCapableBeanFactory類中的createBeanInstance()方法是用來執行例項化Bean物件的操作,該方法會根據Bean定義資訊和配置選項,經過一系列步驟和邏輯判斷,最終建立一個全新的Bean例項。大致步驟如下:
- 根據BeanDefinition載入類並獲取對應的Class物件
- 如果BeanDefinition繫結了一個Supplier,那麼應呼叫Supplier的get方法以獲取一個物件,並將其直接返回。
- 如果在BeanDefinition中存在factoryMethodName屬性,則呼叫該工廠方法以獲取一個bean物件,並將其返回。
- 如果BeanDefinition已經自動構造過了,那麼就呼叫autowireConstructor()方法來自動構造一個物件。
- 呼叫SmartInstantiationAwareBeanPostProcessor介面的determineCandidateConstructors()方法,以確定哪些構造方法是可用的。
- 如果存在可用的構造方法,或者當前BeanDefinition的autowired屬性設定為AUTOWIRE_CONSTRUCTOR,或者BeanDefinition中指定了構造方法引數值,或者在建立Bean時指定了構造方法引數值,那麼將呼叫autowireConstructor()方法來自動構造一個物件。
- 最後,如果不符合前述情況,那麼將根據不帶引數的構造方法來例項化一個物件。
推斷構造方法
我們看下原始碼:
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
throws BeanCreationException {
//前面跟@Lookup相關,我們不看
.....
// Quick check on the concurrent map first, with minimal locking.
Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
if (candidateConstructors == null) {
// Fully synchronized resolution now...
synchronized (this.candidateConstructorsCache) {
candidateConstructors = this.candidateConstructorsCache.get(beanClass);
if (candidateConstructors == null) {
Constructor<?>[] rawCandidates;
try {
// 拿到所有的構造方法
rawCandidates = beanClass.getDeclaredConstructors();
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Resolution of declared constructors on bean Class [" + beanClass.getName() +
"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
}
List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
// 用來記錄required為true的構造方法,一個類中只能有一個required為true的構造方法
Constructor<?> requiredConstructor = null;
// 用來記錄預設無參的構造方法
Constructor<?> defaultConstructor = null;
......
int nonSyntheticConstructors = 0;
// 遍歷每個構造方法
for (Constructor<?> candidate : rawCandidates) {
if (!candidate.isSynthetic()) {
// 記錄一下普通的構造方法
nonSyntheticConstructors++;
}
else if (primaryConstructor != null) {
continue;
}
// 當前遍歷的構造方法是否寫了@Autowired
MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);
if (ann == null) {
// 如果beanClass是代理類,則得到被代理的類的型別,我們不看這種情況
.......
}
// 當前構造方法上加了@Autowired
if (ann != null) {
// 整個類中如果有一個required為true的構造方法,那就不能有其他的加了@Autowired的構造方法
if (requiredConstructor != null) {
throw new BeanCreationException(beanName,
"Invalid autowire-marked constructor: " + candidate +
". Found constructor with 'required' Autowired annotation already: " +
requiredConstructor);
}
boolean required = determineRequiredStatus(ann);
if (required) {
if (!candidates.isEmpty()) {
throw new BeanCreationException(beanName,
"Invalid autowire-marked constructors: " + candidates +
". Found constructor with 'required' Autowired annotation: " +
candidate);
}
// 記錄唯一一個required為true的構造方法
requiredConstructor = candidate;
}
// 記錄所有加了@Autowired的構造方法,不管required是true還是false
// 如果預設無參的構造方法上也加了@Autowired,那麼也會加到candidates中
candidates.add(candidate);
// 從上面程式碼可以得到一個結論,在一個類中,要麼只能有一個required為true的構造方法,要麼只能有一個或多個required為false的方法
}
else if (candidate.getParameterCount() == 0) {
// 記錄唯一一個無參的構造方法
defaultConstructor = candidate;
}
// 有可能存在有參、並且沒有新增@Autowired的構造方法
}
if (!candidates.isEmpty()) {
// Add default constructor to list of optional constructors, as fallback.
// 如果不存在一個required為true的構造方法,則所有required為false的構造方法和無參構造方法都是合格的
if (requiredConstructor == null) {
if (defaultConstructor != null) {
candidates.add(defaultConstructor);
}
else if (candidates.size() == 1 && logger.isInfoEnabled()) {
logger.info("Inconsistent constructor declaration on bean with name '" + beanName +
"': single autowire-marked constructor flagged as optional - " +
"this constructor is effectively required since there is no " +
"default constructor to fall back to: " + candidates.get(0));
}
}
// 如果只存在一個required為true的構造方法,那就只有這一個是合格的
candidateConstructors = candidates.toArray(new Constructor<?>[0]);
}
// 沒有新增了@Autowired註解的構造方法,並且類中只有一個構造方法,並且是有參的
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
}
......
else {
// 如果有多個有參、並且沒有新增@Autowired的構造方法,是會返回空的
candidateConstructors = new Constructor<?>[0];
}
this.candidateConstructorsCache.put(beanClass, candidateConstructors);
}
}
}
return (candidateConstructors.length > 0 ? candidateConstructors : null);
}
接下來,讓我們更深入地探討推斷構造方法,並且我還簡單繪製了一張圖示。
通常情況下,一個類通常只包含一個構造方法:
要麼是無參的構造方法,要麼是有參的構造方法。
如果一個類只有一個無參構造方法,那麼在例項化時只能使用這個構造方法;而如果一個類只有一個有參構造方法,例項化時是否可以使用這個構造方法則取決於具體情況:
- 使用AnnotationConfigApplicationContext進行例項化時,Spring會根據構造方法的引數資訊去尋找對應的bean,並將找到的bean傳遞給構造方法。這樣可以實現依賴注入,確保例項化的物件具有所需的依賴項。
- 當使用ClassPathXmlApplicationContext時,表明使用XML配置檔案的方式來定義bean。在XML中,可以手動指定構造方法的引數值來例項化物件,也可以透過配置autowire=constructor屬性,讓Spring自動根據構造方法的引數型別去尋找對應的bean作為引數值,實現自動裝配。
上面是隻有一個構造方法的情況,那麼如果一個類存在多個構造方法,那麼Spring進行例項化之前,該如何去確定到底用哪個構造方法呢?
- 如果開發者明確定義了他們想要使用的構造方法,那麼程式將會優先使用這個構造方法。
- 如果開發者沒有明確指定他們想要使用的構造方法,系統會自動檢查開發者是否已經配置了讓Spring框架自動選擇構造方法的選項。
- 如果開發者沒有顯式地指定讓Spring框架自動選擇構造方法的情況下,Spring將會預設嘗試使用無參構造方法。如果目標類中沒有無參構造方法,則系統將會丟擲錯誤提示。
針對第一點,開發者可以透過什麼方式來指定使用哪個構造方法呢?
- 在XML中,
標籤用於表示構造方法的引數。開發者可以根據這個標籤確定所需使用的構造方法的引數個數,從而精確指定想要使用的構造方法。 - 透過@Autowired註解,我們可以在構造方法上使用@Autowired註解。因此,當我們在特定構造方法上使用@Autowired註解時,表示開發者希望使用該構造方法。與透過xml方式直接指定構造方法引數值的方式不同,@Autowired註解方式需要Spring透過byType+byName的方式來查詢符合條件的bean作為構造方法的引數值。
- 當然,還有一種情況需要考慮,即當多個構造方法上都標註了@Autowired註解時,此時Spring會丟擲錯誤。然而,由於@Autowired註解具有一個required屬性,預設值為true,因此在一個類中只能有一個構造方法標註了@Autowired或者@Autowired(required=true),否則會導致錯誤。不過,可以有多個構造方法標註@Autowired(required=false)。在這種情況下,Spring會自動從這些構造方法中選擇一個進行注入。
在第二種情況下,如果開發者沒有明確指定要使用的構造方法,Spring將嘗試自動選擇一個合適的構造方法進行注入。這種情況下,只能透過ClassPathXmlApplicationContext實現,因為使用AnnotationConfigApplicationContext無法指定讓Spring自動選擇構造方法。透過ClassPathXmlApplicationContext,可以在XML配置檔案中指定某個bean的autowire屬性為constructor,從而告訴Spring可以自動選擇構造方法。
總結
在Spring中,例項化Bean物件涉及構造方法的呼叫。透過分析原始碼,我們瞭解到例項化的步驟和推斷構造方法的過程。當一個類只有一個構造方法時,Spring會根據具體情況決定是否使用該構造方法。如果一個類存在多個構造方法,就需要根據具體情況具體分析。本文簡單判斷了哪些構造方法是符合例項化的,但在存在多個符合條件的構造方法時,具體使用哪個構造方法尚未討論。因此,我計劃單獨寫一篇文章來詳細討論這個問題。畢竟是太複雜了。