本文已收錄在Github,關注我,緊跟本系列專欄文章,咱們下篇再續!
- 🚀 魔都架構師 | 全網30W技術追隨者
- 🔧 大廠分布式系統/數據中臺實戰專家
- 🏆 主導交易系統百萬級流量調優 & 車聯網平臺架構
- 🧠 AIGC應用開發先行者 | 區塊鏈落地實踐者
- 🌍 以技術驅動創新,我們的征途是改變世界!
- 👉 實戰干貨:編程嚴選網
1 案例:定義的 Bean 缺少隱式依賴
有時把一個類定義成 Bean,又覺得這Bean定義除了加些 Spring 注解,好像平淡無奇!以致在后續使用時隨心定義:
package com.javaedge.spring.service;@Service
public class MyService {private String myServiceName;public MyService(String myServiceName) {this.myServiceName = myServiceName;}public String sayHello() {return "hello Java";}
}
MyService顯式定義了一個構造器。但上面代碼不是永遠都能正確運行,有時報錯:
***********************************
APPLICATION FAILED TO START
***********************************Description:Parameter 0 of constructor in com.javaedge.spring.service.MyService required a bean of type 'java.lang.String' that could not be found.The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)Action:Consider defining a bean of type 'java.lang.String' in your configuration.
2 解惑
創建一個 Bean 時,調AbstractAutowireCapableBeanFactory的
2.1 createBeanInstance
- 尋找構造器
- 通過反射調用構造器創建實例
// Candidate constructors for autowiring?
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {return autowireConstructor(beanName, mbd, ctors, args);
}
2.2 determineConstructorsFromBeanPostProcessors
Spring先執行它獲取構造器:
僅一個有效實現類。然后通過 autowireConstructor 帶著構造器去創建實例。
看ConstructorResolver的
2.3 instantiate
autowireConstructor方法要創建實例:
- 不僅要知道是啥構造器
- 還要知道構造器對應參數
從最后創建實例的方法名也可看出:
private Object instantiate(String beanName, RootBeanDefinition mbd, Constructor<?> constructorToUse, Object[] argsToUse)
argsToUse咋獲取?即當已知構造器ServiceImpl(String serviceName),要創建 ServiceImpl 實例,咋確定serviceName值?
使用 Spring,不能直接顯式 new 創建實例。Spring只能尋找依賴作為構造器調用參數。那這參數咋獲取?
看ConstructorResolver的
2.4 autowireConstructor
argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
可調用 createArgumentArray 構建調用構造器的參數數組,最終從 BeanFactory 獲取 Bean:
即根據參數尋找對應 Bean。本案例中,如找不到對應 Bean,拋異常,提示裝配失敗。
3 修正
定義一個類為 Bean,若再顯式定義構造器,則該 Bean 在構建時,會自動根據構造器參數定義尋找對應 Bean,反射創建該 Bean。可直接定義一個能讓 Spring 裝配給 MyService 構造器參數的 Bean,如:
package com.javaedge.spring.service;@Service
public class MyService {private String myServiceName;public String sayHello() {return "hello Java";}// 該bean裝配給MyService的構造器參數-myServiceName@Beanpublic String myServiceName() {return "MyServiceName";}
}
綜上,使用 Spring 時,別想當然認為定義的 Bean 也可在非 Spring 場合直接new!
若不精通 Spring 的隱式規則,在修正問題后,可能寫出更多看起來好像可運行的程序:
@Service
public class MyService {private String myServiceName;public MyService(String myServiceName) {this.myServiceName = myServiceName;}public MyService(String myServiceName, String otherParam) {this.myServiceName = myServiceName;}
review這段代碼,可能不會發現什么問題,畢竟 String 類型可自動裝配,無非加個 String 參數。但若你知 Spring 內部用反射構建 Bean,就發現問題:存在兩個構造器,都可調用時,到底調用哪個?最終 Spring 無從選擇,只能嘗試調用默認構造器,而默認構造器不存在:
所以報錯: