本系列文章:
推薦閱讀:
本系列文章將會帶你一行行的將Spring的原始碼吃透,推薦閱讀的文章是閱讀原始碼的基礎!
兩個問題
在開始探討原始碼前,我們先思考兩個問題:
1、在Spring中,什麼是Bean?跟物件有什麼區別?
通過new關鍵字,反射,克隆等手段建立出來的就是物件。在Spring中,Bean一定是一個物件,但是物件不一定是一個Bean,一個被建立出來的物件要變成一個Bean要經過很多複雜的工序,例如需要被我們的
BeanPostProcessor
處理,需要經過初始化,需要經過AOP
(AOP
本身也是由後置處理器完成的)等。
2、在建立物件前,Spring還做了其它什麼事情嗎?
我們還是回到流程圖中,其中相關的步驟如下:
在前面的三篇文章中,我們已經分析到了第3-5
步的原始碼,而如果你對Spring原始碼稍有了解的話,就是知道建立物件以及將物件變成一個Bean的過程發生在第3-11
步驟中。中間的五步分別做了什麼呢?
1、registerBeanPostProcessors
就像名字所說的那樣,註冊BeanPostProcessor
,這段程式碼在Spring官網閱讀(八)容器的擴充套件點(三)(BeanPostProcessor)已經分析過了,所以在本文就直接跳過了,如果你沒有看過之前的文章也沒有關係,你只需要知道,在這裡Spring將所有的BeanPostProcessor
註冊到了容器中
2、initMessageSource
初始化容器中的messageSource
,如果程式設計師沒有提供,預設會建立一個org.springframework.context.support.DelegatingMessageSource
,Spring官網閱讀(十一)ApplicationContext詳細介紹(上) 已經介紹過了。
3、initApplicationEventMulticaster
初始化事件分發器,如果程式設計師沒有提供,那麼預設建立一個org.springframework.context.event.ApplicationEventMulticaster
,Spring官網閱讀(十二)ApplicationContext詳解(中)已經做過詳細分析,不再贅述
4、onRefresh
留給子類複寫擴充套件使用
5、registerListeners
註冊事件監聽器,就是將容器中所有實現了org.springframework.context.ApplicationListener
介面的物件放入到監聽器的集合中。
建立物件的原始碼分析
在完成了上面的一些準備工作後,Spring開始來建立Bean了,按照流程,首先被呼叫的就是
finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)
方法,我們就以這個方法為入口,一步步跟蹤原始碼,看看Spring中的Bean到底是怎麼建立出來的,當然,本文主要關注的是建立物件的這個過程,物件變成Bean的流程我們在後續文章中再分析
1、finishBeanFactoryInitialization
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 初始化一個ConversionService用於型別轉換,這個ConversionService會在例項化物件的時候用到
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// 新增一個StringValueResolver,用於處理佔位符,可以看到,預設情況下就是使用環境中的屬性值來替代佔位符中的屬性
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// 建立所有的LoadTimeWeaverAware
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// 靜態織入完成後將臨時的類載入器設定為null,所以除了建立LoadTimeWeaverAware時可能會用到臨時類載入器,其餘情況下都為空
beanFactory.setTempClassLoader(null);
// 將所有的配置資訊凍結
beanFactory.freezeConfiguration();
// 開始進行真正的建立
beanFactory.preInstantiateSingletons();
}
上面的方法最終呼叫了org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
來建立Bean。
其原始碼如下:
2、preInstantiateSingletons
public void preInstantiateSingletons() throws BeansException {
// 所有bd的名稱
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// 遍歷所有bd,一個個進行建立
for (String beanName : beanNames) {
// 獲取到指定名稱對應的bd
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 對不是延遲載入的單例的Bean進行建立
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 判斷是否是一個FactoryBean
if (isFactoryBean(beanName)) {
// 如果是一個factoryBean的話,先建立這個factoryBean,建立factoryBean時,需要在beanName前面拼接一個&符號
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
final FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
// 判斷是否是一個SmartFactoryBean,並且不是懶載入的,就意味著,在建立了這個factoryBean之後要立馬呼叫它的getObject方法建立另外一個Bean
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
// 不是factoryBean的話,我們直接建立就行了
getBean(beanName);
}
}
}
// 在建立了所有的Bean之後,遍歷
for (String beanName : beanNames) {
// 這一步其實是從快取中獲取對應的建立的Bean,這裡獲取到的必定是單例的
Object singletonInstance = getSingleton(beanName);
// 判斷是否是一個SmartInitializingSingleton,最典型的就是我們之前分析過的EventListenerMethodProcessor,在這一步完成了對已經建立好的Bean的解析,會判斷其方法上是否有 @EventListener註解,會將這個註解標註的方法通過EventListenerFactory轉換成一個事件監聽器並新增到監聽器的集合中
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}
上面這段程式碼整體來說應該不難,不過它涉及到了一個點就是factoryBean
,如果你對它不夠了解的話,請參考我之前的一篇文章:Spring官網閱讀(七)容器的擴充套件點(二)FactoryBean
3、doGetBean
從上面的程式碼分析中我們可以知道,Spring最終都會呼叫到getBean
方法,而getBean
並不是真正幹活的,doGetBean
才是。另外doGetBean
可以分為兩種情況
- 建立的是一個
FactoryBean
,此時實際傳入的name = & + beanName
- 建立的是一個普通Bean,此時傳入的
name = beanName
其程式碼如下:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 前面我們說過了,傳入的name可能時& + beanName這種形式,這裡做的就是去除掉&,得到beanName
final String beanName = transformedBeanName(name);
Object bean;
// 這個方法就很牛逼了,通過它解決了迴圈依賴的問題,不過目前我們只需要知道它是從單例池中獲取已經建立的Bean即可,迴圈依賴後面我單獨寫一篇文章
// 方法作用:已經建立的Bean會被放到單例池中,這裡就是從單例池中獲取
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 如果直接從單例池中獲取到了這個bean(sharedInstance),我們能直接返回嗎?
// 當然不能,因為獲取到的Bean可能是一個factoryBean,如果我們傳入的name是 & + beanName 這種形式的話,那是可以返回的,但是我們傳入的更可能是一個beanName,那麼這個時候Spring就還需要呼叫這個sharedInstance的getObject方法來建立真正被需要的Bean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// 在快取中獲取不到這個Bean
// 原型下的迴圈依賴直接報錯
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 核心要義,找不到我們就從父容器中再找一次
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
// 如果不僅僅是為了型別推斷,也就是代表我們要對進行例項化
// 那麼就將bean標記為正在建立中,其實就是將這個beanName放入到alreadyCreated這個set集合中
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 檢查合併後的bd是否是abstract,這個檢查現在已經沒有作用了,必定會通過
checkMergedBeanDefinition(mbd, beanName, args);
// @DependsOn註解標註的當前這個Bean所依賴的bean名稱的集合,就是說在建立當前這個Bean前,必須要先將其依賴的Bean先完成建立
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
// 遍歷所有申明的依賴
for (String dep : dependsOn) {
// 如果這個bean所依賴的bean又依賴了當前這個bean,出現了迴圈依賴,直接報錯
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
// 註冊bean跟其依賴的依賴關係,key為依賴,value為依賴所從屬的bean
registerDependentBean(dep, beanName);
try {
// 先建立其依賴的Bean
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
// 我們目前只分析單例的建立,單例看懂了,原型自然就懂了
if (mbd.isSingleton()) {
// 這裡再次呼叫了getSingleton方法,這裡跟方法開頭呼叫的getSingleton的區別在於,這個方法多傳入了一個ObjectFactory型別的引數,這個ObjectFactory會返回一個Bean
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 省略原型跟域物件的相關程式碼
return (T) bean;
}
配合註釋看這段程式碼應該也不難吧,我們重點關注最後在呼叫的這段方法即可
4、getSingleton(beanName,ObjectFactory)
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
// 從單例池中獲取,這個地方肯定也獲取不到
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 工廠已經在銷燬階段了,這個時候還在建立Bean的話,就直接丟擲異常
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
// 在單例建立前,記錄一下正在建立的單例的名稱,就是把beanName放入到singletonsCurrentlyInCreation這個set集合中去
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 這裡呼叫了singletonFactory的getObject方法,對應的實現就是在doGetBean中的那一段lambda表示式
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
// 省略異常處理
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// 在單例完成建立後,將beanName從singletonsCurrentlyInCreation中移除
// 標誌著這個單例已經完成了建立
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 新增到單例池中
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
分析完上面這段程式碼,我們會發現,核心的建立Bean的邏輯就是在singletonFactory.getObject()
這句程式碼中,而其實現就是在doGetBean
方法中的那一段lambda表示式,如下:
實際就是通過createBean
這個方法建立了一個Bean然後返回,createBean
又幹了什麼呢?
5、createBean
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
RootBeanDefinition mbdToUse = mbd;
// 解析得到beanClass,為什麼需要解析呢?如果是從XML中解析出來的標籤屬性肯定是個字串嘛
// 所以這裡需要載入類,得到Class物件
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
// 對XML標籤中定義的lookUp屬性進行預處理,如果只能根據名字找到一個就標記為非過載的,這樣在後續就不需要去推斷到底是哪個方法了,對於@LookUp註解標註的方法是不需要在這裡處理的,AutowiredAnnotationBeanPostProcessor會處理這個註解
try {
mbdToUse.prepareMethodOverrides();
}
// 省略異常處理...
try {
// 在例項化物件前,會經過後置處理器處理
// 這個後置處理器的提供了一個短路機制,就是可以提前結束整個Bean的生命週期,直接從這裡返回一個Bean
// 不過我們一般不會這麼做,它的另外一個作用就是對AOP提供了支援,在這裡會將一些不需要被代理的Bean進行標記,就本文而言,你可以暫時理解它沒有起到任何作用
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
// 省略異常處理...
try {
// doXXX方法,真正幹活的方法,doCreateBean,真正建立Bean的方法
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
// 省略異常處理...
}
6、doCreateBean
本文只探討物件是怎麼建立的,至於怎麼從一個物件變成了Bean,在後面的文章我們再討論,所以我們主要就關注下面這段程式碼
// 這個方法真正建立了Bean,建立一個Bean會經過 建立物件 > 依賴注入 > 初始化 這三個過程,在這個過程中,BeanPostPorcessor會穿插執行,本文主要探討的是建立物件的過程,所以關於依賴注入及初始化我們暫時省略,在後續的文章中再繼續研究
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
// 這行程式碼看起來就跟factoryBean相關,這是什麼意思呢?
// 在下文我會通過例子介紹下,你可以暫時理解為,這個地方返回的就是個null
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 這裡真正的建立了物件
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 省略依賴注入,初始化
}
這裡我先分析下this.factoryBeanInstanceCache.remove(beanName)
這行程式碼。這裡需要說一句,我寫的這個原始碼分析的系列非常的細節,之所以選擇這樣一個個扣細節是因為我自己在閱讀原始碼過程中經常會被這些問題阻塞,那麼藉著這些文章將自己踩過的坑分享出來可以減少作為讀者的你自己在閱讀原始碼時的障礙,其次也能夠提升自己閱讀原始碼的能力。如果你對這些細節不感興趣的話,可以直接跳過,能把握原始碼的主線即可。言歸正傳,我們回到這行程式碼this.factoryBeanInstanceCache.remove(beanName)
。什麼時候factoryBeanInstanceCache
這個集合中會有值呢?這裡我還是以示例程式碼來說明這個問題,示例程式碼如下:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
}
}
// 沒有做什麼特殊的配置,就是掃描了需要的元件,測試時換成你自己的包名
@ComponentScan("com.dmz.source.instantiation")
@Configuration
public class Config {
}
// 這裡申明瞭一個FactoryBean,並且通過@DependsOn註解申明瞭這個FactoryBean的建立要在orderService之後,主要目的是為了在DmzFactoryBean建立前讓容器發生一次屬性注入
@Component
@DependsOn("orderService")
public class DmzFactoryBean implements FactoryBean<DmzService> {
@Override
public DmzService getObject() throws Exception {
return new DmzService();
}
@Override
public Class<?> getObjectType() {
return DmzService.class;
}
}
// 沒有通過註解的方式將它放到容器中,而是通過上面的DmzFactoryBean來管理對應的Bean
public class DmzService {
}
// OrderService中需要注入dmzService
@Component
public class OrderService {
@Autowired
DmzService dmzService;
}
在這段程式碼中,因為我們明確的表示了DmzFactoryBean
是依賴於orderService
的,所以必定會先建立orderService
再建立DmzFactoryBean
,建立orderService
的流程如下:
其中的屬性注入階段,我們需要細化,也可以畫圖如下:
為orderService進行屬性注入可以分為這麼幾步
-
找到需要注入的注入點,也就是orderService中的dmzService欄位
-
根據欄位的型別以及名稱去容器中查詢符合要求的Bean
-
當遍歷到一個FactroyBean時,為了確定其getObject方法返回的物件的型別需要建立這個FactroyBean(只會到物件級別),然後呼叫這個建立好的FactroyBean的getObjectType方法明確其型別並與注入點需要的型別比較,看是否是一個候選的Bean,在建立這個FactroyBean時就將其放入了
factoryBeanInstanceCache
中。 -
在確定了唯一的候選Bean之後,Spring就會對這個Bean進行建立,建立的過程又經過三個步驟
- 建立物件
- 屬性注入
- 初始化
在建立物件時,因為此時
factoryBeanInstanceCache
已經快取了這個Bean對應的物件,所以直接通過this.factoryBeanInstanceCache.remove(beanName)
這行程式碼就返回了,避免了二次建立物件。
7、createBeanInstance
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
Class<?> beanClass = resolveBeanClass(mbd, beanName);
// 省略異常
// 通過bd中提供的instanceSupplier來獲取一個物件
// 正常bd中都不會有這個instanceSupplier屬性,這裡也是Spring提供的一個擴充套件點,但實際上不常用
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName);
}
// bd中提供了factoryMethodName屬性,那麼要使用工廠方法的方式來建立物件,工廠方法又會區分靜態工廠方法跟例項工廠方法
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// 在原型模式下,如果已經建立過一次這個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) {
return autowireConstructor(beanName, mbd, null, null);
}
else {
return instantiateBean(beanName, mbd);
}
}
// 推斷建構函式
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// 呼叫無參建構函式建立物件
return instantiateBean(beanName, mbd);
}
上面這段程式碼在Spring官網閱讀(一)容器及例項化 已經分析過了,但是當時我們沒有深究建立物件的細節,所以本文將詳細探討Spring中的這個物件到底是怎麼建立出來的,這也是本文的主題。
在Spring官網閱讀(一)容器及例項化 這篇文章中,我畫了下面這麼一張圖
從上圖中我們可以知道Spring在例項化物件的時候有這麼幾種方式
- 通過bd中的supplier屬性
- 通過bd中的factoryMethodName跟factoryBeanName
- 通過建構函式
我們接下來就一一分析其中的細節:
》通過bd中的supplier屬性例項化物件
在Spring官網閱讀(一)容器及例項化 文中介紹過這種方式,因為這種方式我們基本不會使用,並不重要,所以這裡就不再贅述,我這裡就直接給出一個使用示例,大家自行體會吧
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
// 直接註冊一個Bean,並且指定它的supplier就是Service::new
ac.registerBean("service", Service.class,Service::new,zhe'sh);
ac.refresh();
System.out.println(ac.getBean("service"));
}
》通過bd中的factoryMethodName跟factoryBeanName例項化物件
對應程式碼如下:
protected BeanWrapper instantiateUsingFactoryMethod(
String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs);
}
上面這段程式碼主要乾了兩件事
- 建立一個
ConstructorResolver
物件,從類名來看,它是一個構造器解析器 - 呼叫了這個構造器解析器的
instantiateUsingFactoryMethod
方法,這個方法見名知意,使用FactoryMethod
來完成例項化
基於此,我們解決一個問題,ConstructorResolver
是什麼?
ConstructorResolver是什麼?
在要研究一個類前,我們最先應該從哪裡入手呢?很多沒有經驗的同學可能會悶頭看程式碼,但是實際上最好的學習方式是先閱讀類上的javaDoc
ConstructorResolver上的javaDoc如下:
上面這段javaDoc翻譯過來就是這個類就是用來解析建構函式跟工廠方法的代理者,並且它是通過引數匹配的方式來進行推斷構造方法或者工廠方法。
看到這裡不知道小夥伴們是否有疑問,就是明明這個類不僅負責推斷建構函式,還會負責推斷工廠方法,那麼為什麼類名會叫做ConstructorResolver
呢?我們知道Spring的程式碼在業界來說絕對是最規範的,沒有之一,這樣來說的話,這個類最合適的名稱應該是ConstructorAndFactoryMethodResolver
才對,因為它不僅負責推斷了建構函式還負責推斷了工廠方法嘛!
這裡我需要說一下我自己的理解。對於一個Bean,它是通過建構函式完成例項化的,或者通過工廠方法例項化的,其實在這個Bean看來都沒有太大區別,這兩者都可以稱之為這個Bean的構造器,因為通過它們都能構造出一個Bean。所以Spring就把兩者統稱為構造器
了,所以這個類名也就被稱為ConstructorResolver
了。
Spring在很多地方體現了這種實現,例如在XML配置的情況下,不論我們是使用建構函式建立物件還是使用工廠方法建立物件,其引數的標籤都是使用constructor-arg
。比如下面這個例子
<bean id="dmzServiceGetFromStaticMethod"
factory-bean="factoryBean"
factory-method="getObject">
<constructor-arg type="java.lang.String" value="hello" name="s"/>
<constructor-arg type="com.dmz.source.instantiation.service.DmzFactory" ref="factoryBean"/>
</bean>
<!--測試靜態工廠方法建立物件-->
<bean id="service"
class="com.dmz.official.service.MyFactoryBean"
factory-method="staticGet">
<constructor-arg type="java.lang.String" value="hello"/>
</bean>
<bean id="dmzService" class="com.dmz.source.instantiation.service.DmzService">
<constructor-arg name="s" value="hello"/>
</bean>
在對這個類有了大概的瞭解後,我們就需要來分析它的原始碼,這裡我就不把它單獨拎出來分析了,我們藉著Spring的流程看看這個類幹了什麼事情
instantiateUsingFactoryMethod方法做了什麼?
核心目的:推斷出要使用的factoryMethod以及呼叫這個FactoryMethod要使用的引數,然後反射呼叫這個方法例項化出一個物件
這個方法的程式碼太長了,所以我們將它拆分成為一段一段的來分析
方法引數分析
在分析上面的程式碼之前,我們先來看看這個方法的引數都是什麼含義
方法上關於引數的介紹如圖所示
beanName
:當前要例項化的Bean的名稱mbd
:當前要例項化的Bean對應的BeanDefinitionexplicitArgs
:這個引數在容器啟動階段我們可以認定它就是null,只有顯示的呼叫了getBean方法,並且傳入了明確的引數,例如:getBean("dmzService","hello")
這種情況下才會不為null,我們分析這個方法的時候就直接認定這個引數為null即可。
第一段
public BeanWrapper instantiateUsingFactoryMethod(
String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
// 第一段程式碼:建立並初始話一個BeanWrapperImpl
BeanWrapperImpl bw = new BeanWrapperImpl();
this.beanFactory.initBeanWrapper(bw);
// ......
}
BeanWrapperImpl是什麼呢?如果你看過我之前的文章:Spring官網閱讀(十四)Spring中的BeanWrapper及型別轉換,那麼你對這個類應該不會陌生,它就是對Bean進行了一層包裝,並且在建立Bean的時候以及進行屬性注入的時候能夠進行型別轉換。就算你沒看過之前的文章也沒關係,只要記住兩點
- BeanWrapperImpl包裝了一個例項化好的物件
- BeanWrapperImpl能夠對屬性進行型別轉換
其層級關係如下:
回到我們的原始碼分析,我們先來看看new BeanWrapperImpl()
做了什麼事情?
對應程式碼如下:
// 第一步:呼叫空參構造
public BeanWrapperImpl() {
// 呼叫另外一個建構函式,表示要註冊預設的屬性編輯器
this(true);
}
// 這個建構函式表明是否要註冊預設編輯器,上面傳入的值為true,表示需要註冊
public BeanWrapperImpl(boolean registerDefaultEditors) {
super(registerDefaultEditors);
}
// 呼叫到父類的建構函式,確定要使用預設的屬性編輯器
protected AbstractNestablePropertyAccessor(boolean registerDefaultEditors) {
if (registerDefaultEditors) {
registerDefaultEditors();
}
// 對typeConverterDelegate進行初始化
this.typeConverterDelegate = new TypeConverterDelegate(this);
}
總的來說建立的過程非常簡單。第一,確定要註冊預設的屬性編輯器;第二,對typeConverterDelegate屬性進行初始化。
緊接著,我們看看在初始化這個BeanWrapper
做了什麼?
// 初始化BeanWrapper,主要就是將容器中配置的conversionService賦值到當前這個BeanWrapper上
// 同時註冊定製的屬性編輯器
protected void initBeanWrapper(BeanWrapper bw) {
bw.setConversionService(getConversionService());
registerCustomEditors(bw);
}
還記得conversionService
在什麼時候被放到容器中的嗎?就是在finishBeanFactoryInitialization的時候啦~!
對conversionService
屬性完成賦值後就開始註冊定製的屬性編輯器,程式碼如下:
// 傳入的引數就是我們的BeanWrapper,它同時也是一個屬性編輯器登錄檔
protected void registerCustomEditors(PropertyEditorRegistry registry) {
PropertyEditorRegistrySupport registrySupport =
(registry instanceof PropertyEditorRegistrySupport ? (PropertyEditorRegistrySupport) registry : null);
if (registrySupport != null) {
// 這個配置的作用就是在註冊預設的屬性編輯器時,可以增加對陣列到字串的轉換功能
// 預設就是通過","來切割字串轉換成陣列,對應的屬性編輯器就是StringArrayPropertyEditor
registrySupport.useConfigValueEditors();
}
// 將容器中的屬性編輯器註冊到當前的這個BeanWrapper
if (!this.propertyEditorRegistrars.isEmpty()) {
for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) {
registrar.registerCustomEditors(registry);
// 省略異常處理~
}
}
// 這裡我們沒有新增任何的自定義的屬性編輯器,所以肯定為空
if (!this.customEditors.isEmpty()) {
this.customEditors.forEach((requiredType, editorClass) ->
registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass)));
}
}
第二段
public BeanWrapper instantiateUsingFactoryMethod(
String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
// 省略已經分析的第一段程式碼,到這裡已經得到了一個具有型別轉換功能的BeanWrapper
// 例項化這個Bean的工廠Bean
Object factoryBean;
// 工廠Bean的Class
Class<?> factoryClass;
// 靜態工廠方法或者是例項化工廠方法
boolean isStatic;
/*下面這段程式碼就是為上面申明的這三個屬性賦值*/
String factoryBeanName = mbd.getFactoryBeanName();
// 如果建立這個Bean的工廠就是這個Bean本身的話,那麼直接丟擲異常
if (factoryBeanName != null) {
if (factoryBeanName.equals(beanName)) {
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
"factory-bean reference points back to the same bean definition");
}
// 得到建立這個Bean的工廠Bean
factoryBean = this.beanFactory.getBean(factoryBeanName);
if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
throw new ImplicitlyAppearedSingletonException();
}
factoryClass = factoryBean.getClass();
isStatic = false;
}
else {
// factoryBeanName為null,說明是通過靜態工廠方法來例項化Bean的
// 靜態工廠進行例項化Bean,beanClass屬性必須要是工廠的class,如果為空,直接報錯
if (!mbd.hasBeanClass()) {
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
"bean definition declares neither a bean class nor a factory-bean reference");
}
factoryBean = null;
factoryClass = mbd.getBeanClass();
isStatic = true;
}
// 省略後續程式碼
}
小總結:
這段程式碼很簡單,就是確認例項化當前這個Bean的工廠方法是靜態工廠還是例項工廠,如果是例項工廠,那麼找出對應的工廠Bean。
第三段
public BeanWrapper instantiateUsingFactoryMethod(
String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
// 省略第一段,第二段程式碼
// 到這裡已經得到了一個BeanWrapper,明確了例項化當前這個Bean到底是靜態工廠還是例項工廠
// 並且已經確定了工廠Bean
// 最終確定的要用來建立物件的方法
Method factoryMethodToUse = null;
ArgumentsHolder argsHolderToUse = null;
Object[] argsToUse = null;
// 引數分析時已經說過,explicitArgs就是null
if (explicitArgs != null) {
argsToUse = explicitArgs;
}
else {
// 下面這段程式碼是什麼意思呢?
// 在原型模式下,我們會多次建立一個Bean,所以Spring對引數以及所使用的方法做了快取
// 在第二次建立原型物件的時候會進入這段快取的邏輯
// 但是這裡有個問題,為什麼Spring對引數有兩個快取呢?
// 一:resolvedConstructorArguments
// 二:preparedConstructorArguments
// 這裡主要是因為,直接使用解析好的構造的引數,因為這樣會導致建立出來的所有Bean都引用同一個屬性
Object[] argsToResolve = null;
synchronized (mbd.constructorArgumentLock) {
factoryMethodToUse = (Method) mbd.resolvedConstructorOrFactoryMethod;
// 快取已經解析過的工廠方法或者構造方法
if (factoryMethodToUse != null && mbd.constructorArgumentsResolved) {
// resolvedConstructorArguments跟preparedConstructorArguments都是對引數的快取
argsToUse = mbd.resolvedConstructorArguments;
if (argsToUse == null) {
argsToResolve = mbd.preparedConstructorArguments;
}
}
}
if (argsToResolve != null) {
// preparedConstructorArguments需要再次進行解析
argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve);
}
}
// 省略後續程式碼
}
小總結:
上面這段程式碼應該沒什麼大問題,其核心思想就是從快取中取已經解析出來的方法以及引數,這段程式碼只會在原型模式下生效,因為單例的話物件只會建立一次嘛~!最大的問題在於,為什麼在對引數進行快取的時候使用了兩個不同的集合,並且快取後的引數還需要再次解析,這個問題我們暫且放著,不妨帶著這個問題往下看。
因為接下來要分析的程式碼就比較複雜了,所以為了讓你徹底看到程式碼的執行流程,下面我會使用示例+流程圖+文字的方式來分析原始碼。
示例程式碼如下(這個例子覆蓋接下來要分析的所有流程):
配置檔案:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="constructor"><!--這裡開啟自動注入,並且是通過建構函式進行自動注入-->
<!--factoryObject 提供了建立物件的方法-->
<bean id="factoryObject" class="com.dmz.spring.first.instantiation.service.FactoryObject"/>
<!--提供一個用於測試自動注入的物件-->
<bean class="com.dmz.spring.first.instantiation.service.OrderService" id="orderService"/>
<!--主要測試這個物件的例項化過程-->
<bean id="dmzService" factory-bean="factoryObject" factory-method="getDmz" scope="prototype">
<constructor-arg name="name" value="dmz"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="birthDay" value="2020-05-23"/>
</bean>
<!--測試靜態方法例項化物件的過程-->
<bean id="indexService" class="com.dmz.spring.first.instantiation.service.FactoryObject"
factory-method="staticGetIndex"/>
<!--提供這個轉換器,用於轉換dmzService中的birthDay屬性,從字串轉換成日期物件-->
<bean class="org.springframework.context.support.ConversionServiceFactoryBean" id="conversionService">
<property name="converters">
<set>
<bean class="com.dmz.spring.first.instantiation.service.ConverterStr2Date"/>
</set>
</property>
</bean>
</beans>
測試程式碼:
public class FactoryObject {
public DmzService getDmz(String name, int age, Date birthDay, OrderService orderService) {
System.out.println("getDmz with "+"name,age,birthDay and orderService");
return new DmzService();
}
public DmzService getDmz(String name, int age, Date birthDay) {
System.out.println("getDmz with "+"name,age,birthDay");
return new DmzService();
}
public DmzService getDmz(String name, int age) {
System.out.println("getDmz with "+"name,age");
return new DmzService();
}
public DmzService getDmz() {
System.out.println("getDmz with empty arg");
return new DmzService();
}
public static IndexService staticGetIndex() {
return new IndexService();
}
}
public class DmzService {
}
public class IndexService {
}
public class OrderService {
}
public class ConverterStr2Date implements Converter<String, Date> {
@Override
public Date convert(String source) {
try {
return new SimpleDateFormat("yyyy-MM-dd").parse(source);
} catch (ParseException e) {
return null;
}
}
}
/**
* @author 程式設計師DMZ
* @Date Create in 23:14 2020/5/21
* @Blog https://daimingzhi.blog.csdn.net/
*/
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext();
cc.setConfigLocation("application.xml");
cc.refresh();
cc.getBean("dmzService");
// 兩次呼叫,用於測試快取的方法及引數
// cc.getBean("dmzService");
}
}
執行上面的程式碼會發現,程式列印:
getDmz with name,age,birthDay and orderService
具體原因我相信你看了接下來的原始碼分析自然就懂了
第四段
public BeanWrapper instantiateUsingFactoryMethod(
String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
// 第一段程式碼:到這裡已經得到了一個BeanWrapper,並對這個BeanWrapper做了初始化
// 第二段程式碼:明確了例項化當前這個Bean到底是靜態工廠還是例項工廠
// 第三段程式碼:以及從快取中取過了對應了方法以及引數
// 進入第四段程式碼分析,執行到這段程式碼說明是第一次例項化這個物件
if (factoryMethodToUse == null || argsToUse == null) {
// 如果被cglib代理的話,獲取父類的class
factoryClass = ClassUtils.getUserClass(factoryClass);
// 獲取到工廠類中的所有方法,接下來要一步步從這些方法中篩選出來符合要求的方法
Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);
List<Method> candidateList = new ArrayList<>();
// 第一步篩選:之前 在第二段程式碼中已經推斷了方法是靜態或者非靜態的
// 所以這裡第一個要求就是要滿足靜態/非靜態這個條件
// 第二個要求就是必須符合bd中定義的factoryMethodName的名稱
// 其中第二個要求請注意,如果bd是一個configurationClassBeanDefinition,也就是說是通過掃描@Bean註解產生的,那麼在判斷時還會新增是否標註了@Bean註解
for (Method candidate : rawCandidates) {
if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {
candidateList.add(candidate);
}
}
// 將之前得到的方法集合轉換成陣列
// 到這一步得到的其實就是某一個方法的所有過載方法
// 比如dmz(),dmz(String name),dmz(String name,int age)
Method[] candidates = candidateList.toArray(new Method[0]);
// 排序,public跟引數多的優先順序越高
AutowireUtils.sortFactoryMethods(candidates);
// 用來儲存從配置檔案中解析出來的引數
ConstructorArgumentValues resolvedValues = null;
// 是否使用了自動注入,本段程式碼中沒有使用到這個屬性,但是在後面用到了
boolean autowiring = (mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
int minTypeDiffWeight = Integer.MAX_VALUE;
// 可能出現多個符合要求的方法,用這個集合儲存,實際上如果這個集合有值,就會丟擲異常了
Set<Method> ambiguousFactoryMethods = null;
int minNrOfArgs;
// 必定為null,不考慮了
if (explicitArgs != null) {
minNrOfArgs = explicitArgs.length;
}
else {
// 就是說配置檔案中指定了要使用的引數,那麼需要對其進行解析,解析後的值就儲存在resolvedValues這個集合中
if (mbd.hasConstructorArgumentValues()) {
// 通過解析constructor-arg標籤,將引數封裝成了ConstructorArgumentValues
// ConstructorArgumentValues這個類在下文我們專門分析
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
resolvedValues = new ConstructorArgumentValues();
// 解析標籤中的屬性,類似進行型別轉換,後文進行詳細分析
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
}
else {
// 配置檔案中沒有指定要使用的引數,所以執行方法的最小引數個數就是0
minNrOfArgs = 0;
}
}
// 省略後續程式碼....
}
小總結:
因為在例項化物件前必定要先確定具體要使用的方法,所以這裡先做的第一件事就是確定要在哪個範圍內去推斷要使用的factoryMethod呢?
最大的範圍就是這個factoryClass的所有方法,也就是原始碼中的
rawCandidates
其次需要在
rawCandidates
中進一步做推斷,因為在前面第二段程式碼的時候已經確定了是靜態方法還是非靜態方法,並且BeanDefinition
也指定了factoryMethodName,那麼基於這兩個條件這裡就需要對rawCandidates
進一步進行篩選,得到一個candidateList
集合。我們對示例的程式碼進行除錯會發現
確實如我們所料,
rawCandidates
是factoryClass中的所有方法,candidateList
是所有getDmz的過載方法。在確定了推斷factoryMethod的範圍後,那麼接下來要根據什麼去確定到底使用哪個方法呢?換個問題,怎麼區分這麼些過載的方法呢?肯定是根據方法引數嘛!
所以接下來要做的就是去解析要使用的引數了~
對於Spring而言,方法的引數會分為兩種
- 配置檔案中指定的
- 自動注入模式下,需要去容器中查詢的
在上面的程式碼中,Spring就是將配置檔案中指定的引數做了一次解析,對應方法就是
resolveConstructorArguments
。在檢視這個方法的原始碼前,我們先看看
ConstructorArgumentValues
這個類public class ConstructorArgumentValues { // 通過下標方式指定的引數 private final Map<Integer, ValueHolder> indexedArgumentValues = new LinkedHashMap<>(); // 沒有指定下標 private final List<ValueHolder> genericArgumentValues = new ArrayList<>(); // 省略無關程式碼..... }
在前文的註釋中我們也說過了,它主要的作用就是封裝解析
constructor-arg
標籤得到的屬性,解析標籤對應的方法就是org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseConstructorArgElement
,這個方法我就不帶大家看了,有興趣的可以自行閱讀。它主要有兩個屬性
- indexedArgumentValues
- genericArgumentValues
對應的就是我們兩種指定引數的方法,如下:
<bean id="dmzService" factory-bean="factoryObject" factory-method="getDmz" scope="prototype"> <constructor-arg name="name" value="dmz"/> <constructor-arg name="age" value="18"/> <constructor-arg index="2" value="2020-05-23"/> <!-- <constructor-arg name="birthDay" value="2020-05-23"/>--> </bean>
其中的name跟age屬性會被解析為
genericArgumentValues
,而index=2
會被解析為indexedArgumentValues
。在對
ConstructorArgumentValues
有一定認知之後,我們再來看看resolveConstructorArguments
的程式碼:// 方法目的:解析配置檔案中指定的方法引數 // beanName:bean名稱 // mbd:beanName對應的beanDefinition // bw:通過它進行型別轉換 // ConstructorArgumentValues cargs:解析標籤得到的屬性,還沒有經過解析(型別轉換) // ConstructorArgumentValues resolvedValues:已經經過解析的引數 // 返回值:返回方法需要的最小引數個數 private int resolveConstructorArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw, ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) { // 是否有定製的型別轉換器,沒有的話直接使用BeanWrapper進行型別轉換 TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); TypeConverter converter = (customConverter != null ? customConverter : bw); // 構造一個BeanDefinitionValueResolver,專門用於解析constructor-arg中的value屬性,實際上還包括ref屬性,內嵌bean標籤等等 BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); // minNrOfArgs 記錄執行方法要求的最小引數個數,一般情況下就是等於constructor-arg標籤指定的引數數量 int minNrOfArgs = cargs.getArgumentCount(); for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> entry : cargs.getIndexedArgumentValues().entrySet()) { int index = entry.getKey(); if (index < 0) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid constructor argument index: " + index); } // 這是啥意思呢? // 這個程式碼我認為是有問題的,並且我給Spring官方已經提了一個issue,官方將會在5.2.7版本中修復 // 暫且你先這樣理解 // 假設A方法直接在配置檔案中指定了index=3上要使用的引數,那麼這個時候A方法至少需要4個引數 // 但是其餘的3個引數可能不是通過constructor-arg標籤指定的,而是直接自動注入進來的,那麼在配置檔案中我們就只配置了index=3上的引數,也就是說 int minNrOfArgs = cargs.getArgumentCount()=1,這個時候 index=3,minNrOfArgs=1, 所以 minNrOfArgs = 3+1 if (index > minNrOfArgs) { minNrOfArgs = index + 1; } ConstructorArgumentValues.ValueHolder valueHolder = entry.getValue(); // 如果已經轉換過了,直接新增到resolvedValues集合中 if (valueHolder.isConverted()) { resolvedValues.addIndexedArgumentValue(index, valueHolder); } else { // 解析value/ref/內嵌bean標籤等 Object resolvedValue = valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue()); // 將解析後的resolvedValue封裝成一個新的ValueHolder,並將其source設定為解析constructor-arg得到的那個ValueHolder,後期會用到這個屬性進行判斷 ConstructorArgumentValues.ValueHolder resolvedValueHolder = new ConstructorArgumentValues.ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName()); resolvedValueHolder.setSource(valueHolder); resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder); } } // 對getGenericArgumentValues進行解析,程式碼基本一樣,不再贅述 return minNrOfArgs; }
可以看到,最終的解析邏輯就在
resolveValueIfNecessary
這個方法中,那麼這個方法又做了什麼呢?// 這個方法的目的就是將解析constructor-arg標籤得到的value值進行一次解析 // 在解析標籤時ref屬性會被封裝為RuntimeBeanReference,那麼在這裡進行解析時就會去呼叫getBean // 在解析value屬性會會被封裝為TypedStringValue,那麼這裡會嘗試去進行一個轉換 // 關於標籤的解析大家有興趣的話可以去看看org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertyValue // 這裡不再贅述了 public Object resolveValueIfNecessary(Object argName, @Nullable Object value) { // 解析constructor-arg標籤中的ref屬性,實際就是呼叫了getBean if (value instanceof RuntimeBeanReference) { RuntimeBeanReference ref = (RuntimeBeanReference) value; return resolveReference(argName, ref); } // ...... /** * <constructor-arg> * <set value-type="java.lang.String"> * <value>1</value> * </set> * </constructor-arg> * 通過上面set標籤中的value-type屬性對value進行型別轉換, * 如果value-type屬性為空,那麼這裡不會進行型別轉換 */ else if (value instanceof TypedStringValue) { TypedStringValue typedStringValue = (TypedStringValue) value; Object valueObject = evaluate(typedStringValue); try { Class<?> resolvedTargetType = resolveTargetType(typedStringValue); if (resolvedTargetType != null) { return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType); } else { return valueObject; } } catch (Throwable ex) { // Improve the message by showing the context. throw new BeanCreationException( this.beanDefinition.getResourceDescription(), this.beanName, "Error converting typed String value for " + argName, ex); } } // 省略後續程式碼.... }
就我們上面的例子而言,經過
resolveValueIfNecessary
方法並不能產生實際的影響,因為在XML中我們沒有配置ref屬性或者value-type屬性。畫圖如下:
第五段
public BeanWrapper instantiateUsingFactoryMethod(
String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
// 第一段程式碼:到這裡已經得到了一個BeanWrapper,並對這個BeanWrapper做了初始化
// 第二段程式碼:明確了例項化當前這個Bean到底是靜態工廠還是例項工廠
// 第三段程式碼:以及從快取中取過了對應了方法以及引數
// 第四段程式碼:明確了方法需要的最小的引數數量並對配置檔案中的標籤屬性進行了一次解析
// 進入第五段程式碼分析
// 儲存在建立方法引數陣列過程中發生的異常,如果最終沒有找到合適的方法,那麼將這個異常資訊封裝後丟擲
LinkedList<UnsatisfiedDependencyException> causes = null;
// 開始遍歷所有在第四段程式碼中查詢到的符合要求的方法
for (Method candidate : candidates) {
// 方法的引數型別
Class<?>[] paramTypes = candidate.getParameterTypes();
// 候選的方法的引數必須要大於在第四段這推斷出來的最小引數個數
if (paramTypes.length >= minNrOfArgs) {
ArgumentsHolder argsHolder;
// 必定為null,不考慮
if (explicitArgs != null) {
// Explicit arguments given -> arguments length must match exactly.
if (paramTypes.length != explicitArgs.length) {
continue;
}
argsHolder = new ArgumentsHolder(explicitArgs);
}
else {
// Resolved constructor arguments: type conversion and/or autowiring necessary.
try {
// 獲取引數的具體名稱
String[] paramNames = null;
ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
if (pnd != null) {
paramNames = pnd.getParameterNames(candidate);
}
// 根據方法的引數名稱以及配置檔案中配置的引數建立一個引數陣列用於執行工廠方法
argsHolder = createArgumentArray(
beanName, mbd, resolvedValues, bw, paramTypes, paramNames, candidate, autowiring);
}
// 在建立引數陣列的時候可能發生異常,這個時候的異常不能直接丟擲,要確保所有的候選方法遍歷完成,只要有一個方法符合要求即可,但是如果遍歷完所有方法還是沒找到合適的構造器,那麼直接丟擲這些異常
catch (UnsatisfiedDependencyException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Ignoring factory method [" + candidate + "] of bean '" + beanName + "': " + ex);
}
// Swallow and try next overloaded factory method.
if (causes == null) {
causes = new LinkedList<>();
}
causes.add(ex);
continue;
}
// 計算型別差異
// 首先判斷bd中是寬鬆模式還是嚴格模式,目前看來只有@Bean標註的方法解析得到的Bean會使用嚴格模式來計算型別差異,其餘都是使用寬鬆模式
// 嚴格模式下,
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
// 選擇一個型別差異最小的方法
if (typeDiffWeight < minTypeDiffWeight) {
factoryMethodToUse = candidate;
argsHolderToUse = argsHolder;
argsToUse = argsHolder.arguments;
minTypeDiffWeight = typeDiffWeight;
ambiguousFactoryMethods = null;
}
// 省略後續程式碼.......
}
小總結:這段程式碼的核心思想就是根據
第四段
程式碼從配置檔案中解析出來的引數構造方法執行所需要的實際引數陣列。如果構建成功就代表這個方法可以用於例項化Bean,然後計算實際使用的引數跟方法上申明的引數的”差異值“,並在所有符合要求的方法中選擇一個差異值最小的方法接下來,我們來分析方法實現的細節
- 構建方法使用的引數陣列,也就是
createArgumentArray
方法,其原始碼如下:/* beanName:要例項化的Bean的名稱 * mbd:對應Bean的BeanDefinition * resolvedValues:從配置檔案中解析出來的並嘗試過型別轉換的引數 * bw:在這裡主要就是用作型別轉換器 * paramTypes:當前遍歷到的候選的方法的引數型別陣列 * paramNames:當前遍歷到的候選的方法的引數名稱 * executable:當前遍歷到的候選的方法 * autowiring:是否時自動注入 */ private ArgumentsHolder createArgumentArray( String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues, BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable, boolean autowiring) throws UnsatisfiedDependencyException { TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); TypeConverter converter = (customConverter != null ? customConverter : bw); ArgumentsHolder args = new ArgumentsHolder(paramTypes.length); Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length); Set<String> autowiredBeanNames = new LinkedHashSet<>(4); // 遍歷候選方法的引數,跟據方法實際需要的型別到resolvedValues中去匹配 for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { Class<?> paramType = paramTypes[paramIndex]; String paramName = (paramNames != null ? paramNames[paramIndex] : ""); ConstructorArgumentValues.ValueHolder valueHolder = null; if (resolvedValues != null) { // 首先,根據方法引數的下標到resolvedValues中找對應的下標的屬性 // 如果沒找到再根據方法的引數名/型別去resolvedValues查詢 valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders); // 如果都沒找到 // 1.是自動注入並且方法的引數長度正好跟配置中的引數數量相等 // 2.不是自動注入 // 那麼按照順序一次選取 if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) { valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders); } } // 也就是說在配置的引數中找到了合適的值可以應用於這個方法上 if (valueHolder != null) { // 防止同一個引數被應用了多次 usedValueHolders.add(valueHolder); Object originalValue = valueHolder.getValue(); Object convertedValue; // 已經進行過型別轉換就不會需要再次進行型別轉換 if (valueHolder.isConverted()) { convertedValue = valueHolder.getConvertedValue(); args.preparedArguments[paramIndex] = convertedValue; } else { // 嘗試將配置的值轉換成方法引數需要的型別 MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); try { // 進行型別轉換 convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam); } catch (TypeMismatchException ex) { // 丟擲UnsatisfiedDependencyException,在呼叫該方法處會被捕獲 } Object sourceHolder = valueHolder.getSource(); // 只要是valueHolder存在,到這裡這個判斷必定成立 if (sourceHolder instanceof ConstructorArgumentValues.ValueHolder) { Object sourceValue = ((ConstructorArgumentValues.ValueHolder) sourceHolder).getValue(); args.resolveNecessary = true; args.preparedArguments[paramIndex] = sourceValue; } } args.arguments[paramIndex] = convertedValue; args.rawArguments[paramIndex] = originalValue; } else { // 方法執行需要引數,但是resolvedValues中沒有提供這個引數,也就是說這個引數是要自動注入到Bean中的 MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); // 不是自動注入,直接丟擲異常 if (!autowiring) { // 丟擲UnsatisfiedDependencyException,在呼叫該方法處會被捕獲 } try { // 自動注入的情況下,呼叫getBean獲取需要注入的Bean Object autowiredArgument = resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, converter); // 把getBean返回的Bean封裝到本次方法執行時需要的引數陣列中去 args.rawArguments[paramIndex] = autowiredArgument; args.arguments[paramIndex] = autowiredArgument; // 標誌這個引數是自動注入的 args.preparedArguments[paramIndex] = new AutowiredArgumentMarker(); // 自動注入的情況下,在第二次呼叫時,需要重新處理,不能直接快取 args.resolveNecessary = true; } catch (BeansException ex) { // 丟擲UnsatisfiedDependencyException,在呼叫該方法處會被捕獲 } } } // 註冊Bean之間的依賴關係 for (String autowiredBeanName : autowiredBeanNames) { this.beanFactory.registerDependentBean(autowiredBeanName, beanName); if (logger.isDebugEnabled()) { logger.debug("Autowiring by type from bean name '" + beanName + "' via " + (executable instanceof Constructor ? "constructor" : "factory method") + " to bean named '" + autowiredBeanName + "'"); } } return args; }
上面這段程式碼說難也難,說簡單也簡單,如果要徹底看懂它到底幹了什麼還是很有難度的。簡單來說,它就是從第四段程式碼解析出來的引數中查詢當前的這個候選方法需要的引數。如果找到了,那麼嘗試對其進行型別轉換,將其轉換成符合方法要求的型別,如果沒有找到那麼還需要判斷當前方法的這個引數能不能進行自動注入,如果可以自動注入的話,那麼呼叫getBean得到需要的Bean,並將其注入到方法需要的引數中。
第六段
public BeanWrapper instantiateUsingFactoryMethod(
String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
// 第一段程式碼:到這裡已經得到了一個BeanWrapper,並對這個BeanWrapper做了初始化
// 第二段程式碼:明確了例項化當前這個Bean到底是靜態工廠還是例項工廠
// 第三段程式碼:以及從快取中取過了對應了方法以及引數
// 第四段程式碼:明確了方法需要的最小的引數數量並對配置檔案中的標籤屬性進行了一次解析
// 第五段程式碼:到這裡已經確定了可以使用來例項化Bean的方法是哪個
// 省略丟擲異常的程式碼,就是在對推斷出來的方法做驗證
// 1.推斷出來的方法不能為null
// 2.推斷出來的方法返回值不能為void
// 3.推斷出來的方法不能有多個
// 對引數進行快取
if (explicitArgs == null && argsHolderToUse != null) {
argsHolderToUse.storeCache(mbd, factoryMethodToUse);
}
}
try {
Object beanInstance;
if (System.getSecurityManager() != null) {
final Object fb = factoryBean;
final Method factoryMethod = factoryMethodToUse;
final Object[] args = argsToUse;
beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
beanFactory.getInstantiationStrategy().instantiate(mbd, beanName, beanFactory, fb, factoryMethod, args),
beanFactory.getAccessControlContext());
}
else {
// 反射呼叫對應方法進行例項化
// 1.獲取InstantiationStrategy,主要就是SimpleInstantiationStrategy跟CglibSubclassingInstantiationStrategy,其中CglibSubclassingInstantiationStrategy主要是用來處理beanDefinition中的lookupMethod跟replaceMethod。通常來說我們使用的就是SimpleInstantiationStrateg
// 2.SimpleInstantiationStrateg就是單純的通過反射呼叫方法
beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(
mbd, beanName, this.beanFactory, factoryBean, factoryMethodToUse, argsToUse);
}
// beanWrapper在這裡對Bean進行了包裝
bw.setBeanInstance(beanInstance);
return bw;
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean instantiation via factory method failed", ex);
}
}
上面這段程式碼的主要目的就是
- 快取引數,原型可能多次建立同一個物件
- 反射呼叫推斷出來的factoryMethod
》通過建構函式例項化物件
如果上面你對使用factoryMethd進行例項化物件已經足夠了解的話,那麼下面的原始碼分析基本沒有什麼很大區別,我們接著看看程式碼。
首先,我們回到createBeanInstance
方法中,
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 上面的程式碼已經分析過了
// 1.使用supplier來得到一個物件
// 2.通過factotryMethod方法例項化一個物件
// 看起來是不是有點熟悉,在使用factotryMethod建立物件時也有差不多這樣的一段程式碼,看起來就是使用快取好的方法直接建立一個物件
boolean resolved = false;
boolean autowireNecessary = false;
// 不對這個引數進行討論,就認為一直為null
if (args == null) {
synchronized (mbd.constructorArgumentLock) {
// bd中的resolvedConstructorOrFactoryMethod不為空,說明已經解析過構造方法了
if (mbd.resolvedConstructorOrFactoryMethod != null) {
// resolved標誌是否解析過構造方法
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if (resolved) {
// 建構函式已經解析過了,並且這個建構函式在呼叫時需要自動注入引數
if (autowireNecessary) {
// 此時部分解析好的引數已經存在了beanDefinition中,並且建構函式也在bd中
// 那麼在這裡只會從快取中去取建構函式以及引數然後反射呼叫
return autowireConstructor(beanName, mbd, null, null);
}
else {
// 這裡就是直接反射呼叫空參構造
return instantiateBean(beanName, mbd);
}
}
// 推斷出能夠使用的需要引數的建構函式
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 在推斷出來的建構函式中選取一個合適的方法來進行Bean的例項化
// ctors不為null:說明存在1個或多個@Autowired標註的方法
// mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR:說明是自動注入
// mbd.hasConstructorArgumentValues():配置檔案中配置了建構函式要使用的引數
// !ObjectUtils.isEmpty(args):外部傳入的引數,必定為null,不多考慮
// 上面的條件只要滿足一個就會進入到autowireConstructor方法
// 第一個條件滿足,那麼通過autowireConstructor在推斷出來的建構函式中再進一步選擇一個差異值最小的,引數最長的建構函式
// 第二個條件滿足,說明沒有@Autowired標註的方法,但是需要進行自動注入,那麼通過autowireConstructor會去遍歷類中申明的所有建構函式,並查詢一個差異值最小的,引數最長的建構函式
// 第三個條件滿足,說明不是自動注入,那麼要通過配置中的引數去類中申明的所有建構函式中匹配
// 第四個必定為null,不考慮
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// 反射呼叫空參構造
return instantiateBean(beanName, mbd);
}
因為autowireConstructor
方法的執行邏輯跟instantiateUsingFactoryMethod
方法的執行邏輯基本一致,只是將Method
物件換成了Constructor
物件,所以對這個方法我不再做詳細的分析。
我們主要就看看determineConstructorsFromBeanPostProcessors
這個方法吧,這個方法的主要目的就是推斷出候選的構造方法。
determineConstructorsFromBeanPostProcessors方法做了什麼?
// 實際呼叫的就是AutowiredAnnotationBeanPostProcessor中的determineCandidateConstructors方法
// 這個方法看起來很長,但實際確很簡單,就是通過@Autowired註解確定哪些構造方法可以作為候選方法,其實在使用factoryMethod來例項化物件的時候也有這種邏輯在其中,後續在總結的時候我們對比一下
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
throws BeanCreationException {
// 這裡做的事情很簡單,就是將@Lookup註解標註的方法封裝成LookupOverride新增到BeanDefinition中的methodOverrides屬性中,如果這個屬性不為空,在例項化物件的時候不能選用SimpleInstantiationStrateg,而要使用CglibSubclassingInstantiationStrategy,通過cglib代理給方法加一層攔截了邏輯
// 避免重複檢查
if (!this.lookupMethodsChecked.contains(beanName)) {
try {
ReflectionUtils.doWithMethods(beanClass, method -> {
Lookup lookup = method.getAnnotation(Lookup.class);
if (lookup != null) {
Assert.state(this.beanFactory != null, "No BeanFactory available"); // 將@Lookup註解標註的方法封裝成LookupOverride
LookupOverride override = new LookupOverride(method, lookup.value());
try {
// 新增到BeanDefinition中的methodOverrides屬性中
RootBeanDefinition mbd = (RootBeanDefinition)
this.beanFactory.getMergedBeanDefinition(beanName);
mbd.getMethodOverrides().addOverride(override);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(beanName,
"Cannot apply @Lookup to beans without corresponding bean definition");
}
}
});
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);
}
this.lookupMethodsChecked.add(beanName);
}
// 接下來要開始確定到底哪些建構函式能被作為候選者
// 先嚐試從快取中獲取
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) {
// 省略異常資訊
}
List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
// 儲存新增了Autowired註解並且required屬性為true的構造方法
Constructor<?> requiredConstructor = null;
// 空參構造
Constructor<?> defaultConstructor = null;
// 看方法註釋上說明的,這裡除非是kotlin的類,否則必定為null,不做過多考慮,我們就將其當作null
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);
int nonSyntheticConstructors = 0;
// 對類中的所有構造方法進行遍歷
for (Constructor<?> candidate : rawCandidates) {
// 非合成方法
if (!candidate.isSynthetic()) {
nonSyntheticConstructors++;
}
else if (primaryConstructor != null) {
continue;
}
// 查詢方法上是否有Autowired註解
AnnotationAttributes ann = findAutowiredAnnotation(candidate);
if (ann == null) {
// userClass != beanClass說明這個類進行了cglib代理
Class<?> userClass = ClassUtils.getUserClass(beanClass);
if (userClass != beanClass) {
try {
// 如果進行了cglib代理,那麼在父類上再次查詢Autowired註解
Constructor<?> superCtor =
userClass.getDeclaredConstructor(candidate.getParameterTypes());
ann = findAutowiredAnnotation(superCtor);
}
catch (NoSuchMethodException ex) {
// Simply proceed, no equivalent superclass constructor found...
}
}
}
// 說明當前的這個建構函式上有Autowired註解
if (ann != null) {
if (requiredConstructor != null) {
// 省略異常丟擲
}
// 獲取Autowired註解中的required屬性
boolean required = determineRequiredStatus(ann);
if (required) {
// 類中存在多個@Autowired標註的方法,並且某個方法的@Autowired註解上被申明瞭required屬性要為true,那麼直接報錯
if (!candidates.isEmpty()) {
// 省略異常丟擲
}
requiredConstructor = candidate;
}
// 新增到集合中,這個集合儲存的都是被@Autowired註解標註的方法
candidates.add(candidate);
}
// 空參建構函式
else if (candidate.getParameterCount() == 0) {
defaultConstructor = candidate;
}
}
if (!candidates.isEmpty()) {
// 存在多個被@Autowired標註的方法
// 並且所有的required屬性被設定成了false (預設為true)
if (requiredConstructor == null) {
// 存在空參建構函式,注意,空參建構函式可以不被@Autowired註解標註
if (defaultConstructor != null) {
// 將空參建構函式也加入到候選的方法中去
candidates.add(defaultConstructor);
}
// 省略日誌列印
}
candidateConstructors = candidates.toArray(new Constructor<?>[0]);
}
// 也就是說,類中只提供了一個建構函式,並且這個建構函式不是空參建構函式
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
}
// 省略中間兩個判斷,primaryConstructor必定為null,不考慮
// .....
}
else {
// 說明無法推斷出來
candidateConstructors = new Constructor<?>[0];
}
this.candidateConstructorsCache.put(beanClass, candidateConstructors);
}
}
}
return (candidateConstructors.length > 0 ? candidateConstructors : null);
}
這裡我簡單總結下這個方法的作用
獲取到類中的所有建構函式
查詢到被
@Autowired
註解標註的建構函式
- 如果存在多個被
@Autowired
標註的建構函式,並且其required屬性沒有被設定為true,那麼返回這些被標註的函式的集合(空參構造即使沒有新增@Autowired
也會被新增到集合中)- 如果存在多個被
@Autowired
標註的建構函式,並且其中一個的required屬性被設定成了true,那麼直接報錯- 如果只有一個建構函式被
@Autowired
標註,並且其required屬性被設定成了true,那麼直接返回這個建構函式如果沒有被
@Autowired
標註標註的建構函式,但是類中有且只有一個建構函式,並且這個建構函式不是空參建構函式,那麼返回這個建構函式上面的條件都不滿足,那麼
determineCandidateConstructors
這個方法就無法推斷出合適的建構函式了
可以看到,通過AutowiredAnnotationBeanPostProcessor
的determineCandidateConstructors
方法可以處理建構函式上的@Autowired
註解。
但是,請注意,這個方法並不能決定到底使用哪個建構函式來建立物件(即使它只推斷出來一個,也不一定能夠使用),它只是通過@Autowired
註解來確定建構函式的候選者,在建構函式都沒有新增@Autowired
註解的情況下,這個方法推斷不出來任何方法。真正確定到底使用哪個建構函式是交由autowireConstructor
方法來決定的。前文已經分析過了instantiateUsingFactoryMethod
方法,autowireConstructor
的邏輯基本跟它一致,所以這裡不再做詳細的分析。
factoryMethod跟建構函式的比較
整體邏輯比較
從上圖中可以看到,整體邏輯上它們並沒有什麼區別,只是查詢的物件從factoryMethod換成了建構函式
執行細節比較
細節的差異主要體現在推斷方法上
- 推斷factoryMethod
- 推斷建構函式
它們之間的差異我已經在圖中標識出來了,主要就是兩點
- 通過建構函式例項化物件,多了一層處理,就是要處理建構函式上的@Autowired註解以及方法上的@LookUp註解(要決定選取哪一種例項化策略,
SimpleInstantiationStrategy
/CglibSubclassingInstantiationStrategy
)- 在最終的選取也存在差異,對於facotyMehod而言,在寬鬆模式下(
除ConfigurationClassBeanDefinition
外,也就是掃描@Bean得到的BeanDefinition,都是寬鬆模式),會選取一個最精準的方法,在嚴格模式下,會選取一個引數最長的方法- 對於建構函式而言,會必定會選取一個引數最長的方法
關於計算型別差異的補充內容
思考了很久,我還是決定再補充一些內容,就是關於上面兩幅圖的最後一步,對應的核心程式碼如下:
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
if (typeDiffWeight < minTypeDiffWeight) {
factoryMethodToUse = candidate;
argsHolderToUse = argsHolder;
argsToUse = argsHolder.arguments;
minTypeDiffWeight = typeDiffWeight;
ambiguousFactoryMethods = null;
}
-
判斷bd是嚴格模式還是寬鬆模式,上面說過很多次了,bd預設就是寬鬆模式,只要在
ConfigurationClassBeanDefinition
中使用嚴格模式,也就是掃描@Bean標註的方法註冊的bd(對應的程式碼可以參考:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod
方法)我們再看看嚴格模式跟寬鬆模式在計算差異值時的區別
- 寬鬆模式
public int getTypeDifferenceWeight(Class<?>[] paramTypes) { // 計算實際使用的引數跟方法申明的引數的差異值 int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments); // 計算沒有經過型別轉換的引數跟方法申明的引數的差異值 int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024; return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight); } public static int getTypeDifferenceWeight(Class<?>[] paramTypes, Object[] args) { int result = 0; for (int i = 0; i < paramTypes.length; i++) { // 在出現型別轉換時,下面這個判斷才會成立,也就是在比較rawArguments跟paramTypes的差異時才可能滿足這個條件 if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) { return Integer.MAX_VALUE; } if (args[i] != null) { Class<?> paramType = paramTypes[i]; Class<?> superClass = args[i].getClass().getSuperclass(); while (superClass != null) { // 如果我們傳入的值是方法上申明的引數的子類,那麼每多一層繼承關係,差異值加2 if (paramType.equals(superClass)) { result = result + 2; superClass = null; } else if (ClassUtils.isAssignable(paramType, superClass)) { result = result + 2; superClass = superClass.getSuperclass(); } else { superClass = null; } } // 判斷方法的引數是不是一個介面,如果是,那麼差異值加1 if (paramType.isInterface()) { result = result + 1; } } } return result; }
- 嚴格模式(主要應用於@Bean標註的方法對應的BeanDefinition)
public int getAssignabilityWeight(Class<?>[] paramTypes) { // 嚴格模式下,只有三種返回值 // 1.Integer.MAX_VALUE,經過型別轉換後還是不符合要求,返回最大的型別差異 // 因為解析後的引數可能返回一個NullBean(建立物件的方法返回了null,Spring會將其包裝成一個NullBean),不過一般不會出現這種情況,所以我們可以當這種情況不存在 for (int i = 0; i < paramTypes.length; i++) { if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) { return Integer.MAX_VALUE; } } // 2.Integer.MAX_VALUE - 512,進行過了型別轉換才符合要求 for (int i = 0; i < paramTypes.length; i++) { if (!ClassUtils.isAssignableValue(paramTypes[i], this.rawArguments[i])) { return Integer.MAX_VALUE - 512; } } // 3.Integer.MAX_VALUE - 1024,沒有經過型別轉換就已經符合要求了,返回最小的型別差異 return Integer.MAX_VALUE - 1024; }
首先,不管是factoryMethod還是constructor,都是採用上面的兩個方法來計算型別差異,但是正常來說,只有factoryMethod會採用到嚴格模式(除非程式設計師手動干預,比如通過Bean工廠後置處理器修改了bd中的屬性,這樣通常來說沒有很大意義)
所以我們分為三種情況討論
1、factoryMethod+寬鬆模式
這種情況下,會選取一個最精確的方法,同時方法的引數要儘量長
測試程式碼:
public class FactoryObject { public DmzService getDmz() { System.out.println(0); return new DmzService(); } public DmzService getDmz(OrderService indexService) { System.out.println(1); return new DmzService(); } public DmzService getDmz(OrderService orderService, IndexService indexService) { System.out.println(2); return new DmzService(); } public DmzService getDmz(OrderService orderService, IndexService indexService,IA ia) { System.out.println(3); return new DmzService(); } } public class ServiceImpl implements IService { } public class IAImpl implements IA { }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="constructor"><!--必須要開啟自動注入,並且是通過建構函式進行自動注入,否則選用無參構造--> <!--factoryObject 提供了建立物件的方法--> <bean id="factoryObject" class="com.dmz.spring.instantiation.service.FactoryObject"/> <bean class="com.dmz.spring.instantiation.service.OrderService" id="orderService"/> <bean id="dmzService" factory-bean="factoryObject" factory-method="getDmz" /> <bean class="com.dmz.spring.instantiation.service.ServiceImpl" id="iService"/> <bean class="com.dmz.spring.instantiation.service.IndexService" id="indexService"/> </beans>
/** * @author 程式設計師DMZ * @Date Create in 23:59 2020/6/1 * @Blog https://daimingzhi.blog.csdn.net/ */ public class XMLMain { public static void main(String[] args) { ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("application.xml"); } }
執行程式發現,選用了第三個(
getDmz(OrderService orderService, IndexService indexService)
)構造方法。雖然最後一個方法的引數更長,但是因為其方法申明的引數上存在介面,所以它的差異值會大於第三個方法,因為不會被選用2、factoryMethod+嚴格模式
這種情況下,會選取一個引數儘量長的方法
測試程式碼:
/** * @author 程式設計師DMZ * @Date Create in 6:28 2020/6/1 * @Blog https://daimingzhi.blog.csdn.net/ */ @ComponentScan("com.dmz.spring.instantiation") @Configuration public class Config { @Bean public DmzService dmzService() { System.out.println(0); return new DmzService(); } @Bean public DmzService dmzService(OrderService indexService) { System.out.println(1); return new DmzService(); } @Bean public DmzService dmzService(OrderService orderService, IndexService indexService) { System.out.println(2); return new DmzService(); } @Bean public DmzService dmzService(OrderService orderService, IndexService indexService, IA ia) { System.out.println("config " +3); return new DmzService(); } @Bean public DmzService dmzService(OrderService orderService, IndexService indexService, IA ia, IService iService) { System.out.println("config " +4); return new DmzService(); } } /** * @author 程式設計師DMZ * @Date Create in 6:29 2020/6/1 * @Blog https://daimingzhi.blog.csdn.net/ */ public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); ac.register(Config.class); ac.refresh(); } }
執行程式,發現選用了最後一個建構函式,這是因為在遍歷候選方法時,會先遍歷引數最長的,而在計算型別差異時,因為嚴格模式下,上面所有方法的差異值都是一樣的,都會返回
Integer.MAX_VALUE - 1024
。實際上,在不進行手動干預的情況下,都會返滬這個值。3、建構函式+寬鬆模式
這種情況下,也會選取一個引數儘量長的方法
之所以會這樣,主要是因為在
autowireConstructor
方法中進行了一次短路判斷,如下所示:在上圖中,如果已經找到了合適的方法,那麼直接就不會再找了,而在遍歷的時候是從引數最長的方法開始遍歷的,測試程式碼如下:
@Component public class DmzService { // 沒有新增@Autowired註解,也會被當作候選方法 public DmzService(){ System.out.println(0); } @Autowired(required = false) public DmzService(OrderService orderService) { System.out.println(1); } @Autowired(required = false) public DmzService(OrderService orderService, IService iService) { System.out.println(2); } @Autowired(required = false) public DmzService(OrderService orderService, IndexService indexService, IService iService,IA ia) { System.out.println("DmzService "+3); } } /** * @author 程式設計師DMZ * @Date Create in 6:29 2020/6/1 * @Blog https://daimingzhi.blog.csdn.net/ */ public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); ac.register(Config.class); ac.refresh(); } }
這篇文章就到這裡啦~~!
文章很長,希望你耐心看完,碼字不易,如果有幫助到你的話點個贊吧~!
掃描下方二維碼,關注我的公眾號,更多精彩文章在等您!~~