Spring創(chuàng)建BeanDefinition之路徑掃描詳解
一、從示例開始
當我們創(chuàng)建AnnotationConfigApplicationContext對象時,Spring底層到底做了些什么?
來看下面示例。
package com.xiakexing;
import com.xiakexing.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.test();
}
}package com.xiakexing;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value = "com.xiakexing")
public class AppConfig {
}package com.xiakexing.service;
import org.springframework.stereotype.Component;
@Component
public class UserService {
public void test() {
System.out.println("hello spring");
}
}
我們猜測這幾行代碼執(zhí)行邏輯:
- new AnnotationConfigApplicationContext(AppConfig.class)時,從AppConfig類解析掃描路徑即@ComponentScan;
- 遍歷掃描路徑下的所有Java類,如果某個類上有@Component、@Service等注解,Spring就為這個類創(chuàng)建BeanDefinition,保存到Map中,比如Map<String, Class>,key是根據規(guī)則生成的beanName,value就是當前類的class對象。
- context.getBean("userService")時,Spring根據beanName找到類的class對象,反射調用構造器創(chuàng)建對象。
帶著上面的猜想,我們來看看源碼。本文暫且關注路徑掃描的實現(xiàn),隨后的文章將講解BeanDefinition的創(chuàng)建過程。
二、創(chuàng)建AnnotationConfigApplicationContext
構造方法的入參是componentClasses,即可以傳入多個配置類。

this()中創(chuàng)建了AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner,將用于掃描指定路徑下的類,創(chuàng)建BeanDefinition。

JFR 是 Java Flight Record (Java飛行記錄),是JVM 內置的基于事件的JDK監(jiān)控記錄框架。StartupStep是Spring基于JFR對運行過程的監(jiān)控,閱讀源碼時可忽略它。
注意,AnnotationConfigApplicationContext間接實現(xiàn)了BeanDefinitionRegistry接口,具備向容器中注冊BeanDefinition的能力。
在創(chuàng)建ClassPathBeanDefinitionScanner對象時,指定了使用DefaultFilters:將掃描所有帶有@Component注解的類。



總結:this()僅僅實例化了容器對象,創(chuàng)建了Reader、Scanner,用于解析類信息。
三、注冊Configuration類
3.1 創(chuàng)建BeanDefinition
register(componentClasses),顯然是將配置類注冊到容器中。


來看AnnotatedBeanDefinitionReader#doRegisterBean的核心邏輯:
- 為配置類創(chuàng)建AnnotatedGenericBeanDefinition對象;
- 處理@Conditional,如果條件不滿足,將舍棄這個類;
- 給BeanDefinition對象屬性賦值;
- 生成beanName,解析@Lazy、@Primary、@DependsOn等注解;
- 創(chuàng)建BeanDefinitionHolder對象,發(fā)起注冊。

3.2 注冊BeanDefinition
BeanDefinitionHolder類只是對BeanDefinition的包裝,僅有三個屬性:beanDefinition、beanName和aliases。

在BeanDefinitionReaderUtils#registerBeanDefinition中

最終會調用DefaultListableBeanFactory#registerBeanDefinition方法,執(zhí)行這幾行代碼:

看到了BeanFactory的兩個核心數(shù)據結構:
- beanDefinitionMap保存了beanName與beanDefinition的映射;
- beanDefinitionNames保存了所有的beanName


果然與我們當初的猜想一致。
至此,配置類已被添加到beanDefinitionMap中,可是@ComponentScan指定的包路徑,在哪兒被處理了呢?
四、掃描包路徑
先說結論:@ComponentScan包路徑下的類,是在ClassPathBeanDefinitionScanner#scan中被處理的。
先來看AnnotationConfigApplicationContext的另一個構造方法:入參就是指定包路徑。

轉調到ClassPathBeanDefinitionScanner#scan。

基于配置類創(chuàng)建AnnotationConfigApplicationContext時,是在哪兒調了scan()或doScan()呢?答案就在refresh()中。
4.1 BeanFactoryPostProcessor接口
先看類注釋:
Factory hook that allows for custom modification of an application context's bean definitions, adapting the bean property values of the context's underlying bean factory.
工廠鉤子,允許自定義修改應用程序上下文的bean定義,調整上下文的底層bean工廠的bean屬性值。
A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances.
BeanFactoryPostProcessor可以與bean定義交互和修改,但不能與bean實例交互。
可見,該接口是BeanFactory的后置處理器,在創(chuàng)建Bean實例前,干涉BeanDefinition創(chuàng)建和更新。
僅有一個方法:
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
該接口有個重要的子接口BeanDefinitionRegistryPostProcessor,能夠向容器注冊更多的BeanDefinition。
Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in.
對標準BeanFactoryPostProcessor SPI的擴展,允許在常規(guī)BeanFactoryPostProcessor檢測開始之前注冊進一步的bean定義。

4.2 ConfigurationClassPostProcessor類
源碼中,BeanDefinitionRegistryPostProcessor接口僅有唯一實現(xiàn)ConfigurationClassPostProcessor。

在ConfigurationClassPostProcessor#processConfigBeanDefinitions中,
檢查已注冊的每一個BeanDefinition,是否是候選配置類(或組件),滿足以下任意條件即可:
- 類上有@Configuration注解;
- 類上有以下任意一個注解;

- 類中有@Bean注解的方法;
得到Set<BeanDefinitionHolder> candidates后,會調用ConfigurationClassParser.parse()

接下來會遍歷處理每一個候選類
在ConfigurationClassParser#doProcessConfigurationClass中,解析@ComponentScan、@ComponentScans注解;

- 執(zhí)行Filter邏輯后,得到basePackages路徑集;
- 調用ClassPathBeanDefinitionScanner#doScan,為路徑下的Bean創(chuàng)建BeanDefinition,并注冊到容器中。

關于doScan方法的詳細邏輯,我們下一篇再看。
五、邏輯閉環(huán)
要用ConfigurationClassPostProcessor來處理配置類,Spring容器中就得先有該類的實例。那么,這個類是何時注冊到容器中的?
答案就在new AnnotatedBeanDefinitionReader(this)中:


為ConfigurationClassPostProcessor創(chuàng)建BeanDefinition并注冊。

當執(zhí)行AbstractApplicationContext#refresh時,其中有一步是調用容器中BeanFactoryPostProcessor接口所有實現(xiàn)。

此時,ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry將被執(zhí)行。

流程圖

總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- Spring中ImportBeanDefinitionRegistrar源碼和使用方式
- 解析Spring框架中的XmlBeanDefinitionStoreException異常情況
- 解析和解決org.springframework.beans.factory.NoSuchBeanDefinitionException異常問題
- SpringBoot實現(xiàn)ImportBeanDefinitionRegistrar動態(tài)注入
- Spring配置文件解析之BeanDefinitionParserDelegate詳解
- Spring配置文件解析之BeanDefinitionDocumentReader詳解
- Spring配置文件解析之BeanDefinitionReader詳解
相關文章
關于Spring?Validation數(shù)據校檢的使用流程分析
在實際項目中,對客戶端傳遞到服務端的參數(shù)進行校驗至關重要,SpringValidation提供了一種便捷的方式來實現(xiàn)這一需求,通過在POJO類的屬性上添加檢查注解,本文給大家介紹Spring?Validation數(shù)據校檢的使用流程,感興趣的朋友一起看看吧2024-11-11
Java使用POI從Excel讀取數(shù)據并存入數(shù)據庫(解決讀取到空行問題)
有時候需要在java中讀取excel文件的內容,專業(yè)的方式是使用java POI對excel進行讀取,這篇文章主要給大家介紹了關于Java使用POI從Excel讀取數(shù)據并存入數(shù)據庫,文中介紹的辦法可以解決讀取到空行問題,需要的朋友可以參考下2023-12-12
一天時間用Java寫了個飛機大戰(zhàn)游戲,朋友直呼高手
前兩天我發(fā)現(xiàn)論壇有兩篇飛機大戰(zhàn)的文章異?;鸨?但都是python寫的,竟然不是我大Java,說實話作為老java選手,我心里是有那么一些失落的,今天特地整理了這篇文章,需要的朋友可以參考下2021-05-05

