Spring?IOC中的組件掃描
版本 Spring Framework 6.0.9?
1. 前言
通過(guò)自動(dòng)掃描,Spring 會(huì)自動(dòng)從掃描指定的包及其子包下的所有類(lèi),并根據(jù)類(lèi)上的特定注解將該類(lèi)裝配到容器中,而無(wú)需在 XML 配置文件或 Java 配置類(lèi)中逐一聲明每一個(gè) Bean。
支持的注解
Spring 支持一系列注解,用于標(biāo)記哪些類(lèi)應(yīng)被自動(dòng)掃描并作為 Bean 管理。這些注解通常包含:
- @Component:基礎(chǔ)注解,標(biāo)記一個(gè)類(lèi)作為 Spring 組件。所有其他特殊用途的組件注解都繼承自此注解。
- @Repository:用于標(biāo)注 DAO(Data Access Object)類(lèi),除了具備 @Component 的功能外,還為數(shù)據(jù)訪問(wèn)異常提供了特殊的翻譯機(jī)制。
- @Service:用于標(biāo)注業(yè)務(wù)層(Service)類(lèi),強(qiáng)調(diào)這是一個(gè)業(yè)務(wù)相關(guān)的組件。
- @Controller:用于標(biāo)注 MVC 架構(gòu)中的控制器類(lèi),通常在 Spring MVC 中使用,與 Spring Web 相關(guān)的請(qǐng)求處理邏輯相關(guān)聯(lián)。
- @Configuration:用于標(biāo)注配置類(lèi),這類(lèi)類(lèi)通常包含 @Bean 方法,用于定義其他 Bean。
- @RestController:結(jié)合了 @Controller 和 @ResponseBody,適用于構(gòu)建 RESTful Web 服務(wù)的控制器。
例子相關(guān)實(shí)體類(lèi)
控制層(例子中沒(méi)引入spring-web包,注解先使用@Component替代)

服務(wù)層

數(shù)據(jù)訪問(wèn)層

2. 基于xml
2.1 使用
在 Spring 的XML配置文件中,通過(guò) <context:component-scan> 標(biāo)簽開(kāi)啟自動(dòng)掃描功能。我們可以配置 base-package 指定掃描路徑。



輸出信息說(shuō)明每個(gè)實(shí)體類(lèi)都已加載到容器。
<context:component-scan> 其他配置元素:
- resource-pattern:可選屬性,用于指定在掃描包路徑下應(yīng)匹配的資源模式。默認(rèn)值為 “**/*.class”,即掃描所有 .class 文件??梢愿鶕?jù)需要修改此模式,例如,僅掃描特定目錄下的類(lèi)。
- use-default-filters:布爾屬性,默認(rèn)值為 true。決定是否啟用默認(rèn)的過(guò)濾規(guī)則,即查找?guī)в?@Component、@Repository、@Service、@Controller、@Configuration 等注解的類(lèi)。如果設(shè)置為 false,則需要手動(dòng)配置 和 來(lái)指定掃描規(guī)則。
- scope-resolver:指定一個(gè)自定義的 ScopeMetadataResolver 實(shí)現(xiàn)類(lèi),用于確定掃描到的 Bean 的作用域。默認(rèn)情況下,Spring 使用 AnnotationScopeMetadataResolver,根據(jù)類(lèi)上的 @Scope 注解來(lái)確定作用域。
- scoped-proxy:指定是否為掃描到的帶有作用域注解的 Bean 創(chuàng)建代理??赡艿闹蛋ǎ?ul>
- no(默認(rèn)):不創(chuàng)建代理。
- interfaces:為實(shí)現(xiàn)了接口的 Bean 創(chuàng)建 JDK 動(dòng)態(tài)代理。
- targetClass:為未實(shí)現(xiàn)接口或需要保留原始類(lèi)類(lèi)型的 Bean 創(chuàng)建 CGLIB 代理。
2.2 掃描原理
- 當(dāng)我們使用ClassPathXmlApplicationContext作為IOC容器,在refresh方法的obtainFreshBeanFactory階段會(huì)創(chuàng)建一個(gè)bean工廠DefaultListableBeanFactory。
- 當(dāng)前上下文調(diào)用loadBeanDefinitions方法,根據(jù)容器的配置文件加載bean定義。
- 配置文件中通過(guò) <context:component-scan> 標(biāo)簽開(kāi)啟自動(dòng)掃描功能,屬于其他命名空間,托管給 ComponentScanBeanDefinitionParser處理。
- ComponentScanBeanDefinitionParser#parse方法中創(chuàng)建一個(gè)ClassPathBeanDefinitionScanner對(duì)象,調(diào)用其scan方法掃描組件并加載到bean工廠中。

其他命名空間
Spring框架除了核心的XML命名空間外,還提供了多個(gè)擴(kuò)展命名空間(除beans以外的命名空間),以支持特定的功能模塊和簡(jiǎn)化配置。
- http://www.springframework.org/schema/beans:核心命名空間,這是最基礎(chǔ)且最常用的命名空間,用于定義bean、設(shè)置屬性、注入依賴(lài)、配置構(gòu)造函數(shù)參數(shù)、初始化方法、銷(xiāo)毀方法、自動(dòng)裝配等基本IoC容器功能。http://www.springframework.org/schema/context:
- context命名空間,提供了對(duì)Java配置類(lèi)、自動(dòng)掃描、注解驅(qū)動(dòng)的bean定義、屬性占位符替換、資源加載等上下文相關(guān)的高級(jí)功能的支持。通過(guò)此命名空間,可以簡(jiǎn)化基于注解的配置,如@Component、@Autowired等。
在實(shí)例化XmlReaderContext時(shí),DefaultBeanDefinitionDocumentReader會(huì)創(chuàng)建一個(gè)命名空間解析器DefaultNamespaceHandlerResolver,緩存到XmlReaderContext,當(dāng)Reader解析配置文件時(shí)發(fā)現(xiàn)存在其他命名空間時(shí),通過(guò)DefaultNamespaceHandlerResolver加載 META-INF/spring.handlers 路徑下的其他命名空間處理器,獲取對(duì)應(yīng)的處理器處理。
XmlReaderContext 是Spring框架中用于處理XML配置文件解析過(guò)程中的上下文對(duì)象,為DefaultBeanDefinitionDocumentReader在解析和處理XML配置文件時(shí)提供了必要的環(huán)境支持。

context命名空間處理器(ComponentScanBeanDefinitionParser)
- 獲取基礎(chǔ)包名basePackages。
- 創(chuàng)建類(lèi)路徑 Bean 定義掃描程序ClassPathBeanDefinitionScanner。
- 遍歷掃描包名,查找被@Component及其派生注解修飾的類(lèi)。將它們封裝為BeanDefinitionHolder對(duì)象。
- 往bean工廠加載特定注解后置處理器的bean定義(internalConfigurationAnnotationProcessor、internalAutowiredAnnotationProcess、internalCommonAnnotationProcessor、internalEventListenerProcessor、internalEventListenerFactory),觸發(fā)組件注冊(cè)事件.
獲取基礎(chǔ)包名basePackages
- element.getAttribute(BASE_PACKAGE_ATTRIBUTE)方法從對(duì)象中獲取base-package的屬性值。
- parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage)方法解析占位符,獲取解析值。
- StringUtils.tokenizeToStringArray(basePackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)方法將基礎(chǔ)包名按逗號(hào)、分號(hào)或空格等常見(jiàn)分隔符拆分為一個(gè)字符串?dāng)?shù)組basePackages。

創(chuàng)建組件掃描器
ComponentScanBeanDefinitionParser#configureScanner方法通過(guò)對(duì)XML元素element的各項(xiàng)屬性(use-default-filters、resource-pattern、name-generator、scope-resolver、scoped-proxy)和子元素(include-filter、exclude-filter)進(jìn)行解析,創(chuàng)建一個(gè)定制化的掃描器ClassPathBeanDefinitionScanner。

另外通過(guò)ClassPathBeanDefinitionScanner構(gòu)造函數(shù)實(shí)例化時(shí),會(huì)創(chuàng)建一個(gè)Component類(lèi)型的注解類(lèi)型過(guò)濾器AnnotationTypeFilter添加到includeFilters屬性中,用于將類(lèi)與@Component進(jìn)行匹配。

掃描組件
ClassPathBeanDefinitionScanner#doScan掃描指定的basePackages包及其子包,查找并處理符合要求的bean定義,最終將它們封裝為BeanDefinitionHolder對(duì)象并返回,bean定義類(lèi)型是ScannedGenericBeanDefinition。

掃描核心方法ClassPathScanningCandidateComponentProvider#findCandidateComponents用于在指定的basePackage包及其子包下查找符合要求的候選bean組件(BeanDefinition),支持索引查找(效率更高)或按傳統(tǒng)方式查找(獲取指定包下所有class對(duì)象篩選)。

我們只看傳統(tǒng)方式查找方式,ClassPathScanningCandidateComponentProvider#scanCandidateComponents方法用于在給定的basePackage及其子包下通過(guò)類(lèi)路徑掃描機(jī)制查找候選bean組件(BeanDefinition).

ClassPathScanningCandidateComponentProvider#isCandidateComponent方法用于判斷給定的MetadataReader所代表的類(lèi)是否滿(mǎn)足候選bean組件的條件,在不添加其他排除過(guò)濾器、包含過(guò)濾器或配置,上面創(chuàng)建組件掃描器過(guò)程中,已知添加了一個(gè)AnnotationTypeFilter包含過(guò)濾器,在此處使用,匹配被@Component注解的類(lèi)。

觸發(fā)組件注冊(cè)事件
ComponentScanBeanDefinitionParser#registerComponents負(fù)責(zé)將一組BeanDefinition注冊(cè)到Spring IoC容器中,并處理與之相關(guān)的XML元素及注解配置處理器。實(shí)際上做了兩件事
往bean工廠加載特定注解后置處理器的bean定義觸發(fā)組件注冊(cè)事件,一般情況下是EmptyReaderEventListener,空方法。

3. 全注解開(kāi)發(fā)
AnnotationConfigApplicationContext 是 Spring Framework 中的一個(gè)核心類(lèi),用于創(chuàng)建和管理基于 Java 注解的 Spring 應(yīng)用程序上下文。AnnotationConfigApplicationContext 有兩種開(kāi)啟組件掃描的方式
- 直接指定包路徑
- 通過(guò)@ComponentScan注解
3.1 通過(guò)指定包路徑開(kāi)啟掃描
使用


掃描原理
接受一個(gè)字符串?dāng)?shù)組參數(shù) basePackages的有參構(gòu)造邏輯分成三部分:
- this(); 調(diào)用無(wú)參構(gòu)造函數(shù)
- scan(basePackages); 執(zhí)行類(lèi)路徑掃描
- refresh(); 啟動(dòng)應(yīng)用上下文

無(wú)參構(gòu)造函數(shù)實(shí)例化組件掃描器 ClassPathBeanDefinitionScanner,用于在類(lèi)路徑中掃描并注冊(cè)基于注解的 Bean 定義(如 @Component、@Service、@Repository、@Controller 等)。

調(diào)用ClassPathBeanDefinitionScanner#scan方法掃描組件,與基于xml掃描邏輯一樣,調(diào)用的是ClassPathBeanDefinitionScanner#scan方法,所以bean定義創(chuàng)建的類(lèi)型也是ScannedGenericBeanDefinition類(lèi)。



3.2 通過(guò)Java配置類(lèi)開(kāi)啟掃描
使用

c


掃描原理
接受一個(gè)類(lèi)型為 Class<?> 的可變參數(shù)數(shù)組 componentClasses的有參構(gòu)造邏輯分成三部分:
- this(); 調(diào)用無(wú)參構(gòu)造函數(shù)
- register(componentClasses); 注冊(cè)指定的組件
- refresh(); 啟動(dòng)應(yīng)用上下文

第一部分雖然與接受字符串的有參構(gòu)造(指定掃描路徑)一樣調(diào)用了無(wú)參構(gòu)造,但我們這里只關(guān)注 帶注釋的 Bean 定義讀取器AnnotatedBeanDefinitionReader,而不是類(lèi)路徑 Bean 定義掃描程器ClassPathBeanDefinitionScanner。

實(shí)例化AnnotatedBeanDefinitionReader過(guò)程中,會(huì)調(diào)用AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)方法,往bean工廠注冊(cè)一個(gè)bean定義后置處理器ConfigurationClassPostProcessor。


第二部分注冊(cè)Java配置類(lèi)ScanConfig,實(shí)例化并初始化bean定義類(lèi)AnnotatedGenericBeanDefinition,并加載到bean工廠中。




第三部分在refresh方法invokeBeanFactoryPostProcessors階段,會(huì)調(diào)用在第一部分注冊(cè)的ConfigurationClassPostProcessor(在當(dāng)前階段會(huì)調(diào)用getBean方法實(shí)例化)后置處理。

ConfigurationClassPostProcessor 是 Spring 框架中一個(gè)重要的 BeanDefinitionRegistryPostProcessor 實(shí)現(xiàn),主要負(fù)責(zé)處理帶有 @Configuration 注解的類(lèi)以及它們內(nèi)部定義的 @Bean 方法和其他相關(guān)注解。postProcessBeanDefinitionRegistry方法會(huì)篩選出有效的@configuration類(lèi),調(diào)用配置類(lèi)解析器ConfigurationClassParser的parse方法解析。


從第二部分知道,ScanConfig生成的bean定義類(lèi)是AnnotatedGenericBeanDefinition,是AnnotatedBeanDefinition的子類(lèi)。debug源碼到處理@ComponentScan注解(this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());),說(shuō)明ConfigurationClassParser處理邏輯委托給componentScanParser,調(diào)用其parse方法處理。




這里的this.componentScanParser是ComponentScanAnnotationParser類(lèi),在調(diào)用ConfigurationClassParser構(gòu)造函數(shù)實(shí)例化時(shí)創(chuàng)建。

ComponentScanAnnotationParser#parse方法中的邏輯就很熟悉了,可以對(duì)標(biāo)context命名空間處理各個(gè)屬性。在方法中是新建了一個(gè)ClassPathBeanDefinitionScanner類(lèi),而不是使用AnnotationConfigApplicationContext無(wú)參構(gòu)造中實(shí)例化的ClassPathBeanDefinitionScanner,最后執(zhí)行doscan方法掃描組件。

總結(jié)
總結(jié)一下通過(guò)@ComponentScan開(kāi)啟掃描流程.
- this();
- 在實(shí)例化AnnotatedBeanDefinitionReader過(guò)程中,往bean工廠加載了ConfigurationClassPostProcessor的bean定義
- register(componentClasses);
- 往bean工廠加載了包含@Configuration、@ComponentScan注解的ScanConfig配置類(lèi) 的bean定義。
- refresh();
- 在invokeBeanFactoryPostProcessors階段實(shí)例化了ConfigurationClassPostProcessor,并調(diào)用其postProcessBeanDefinitionRegistry方法處理@Configuration配置類(lèi)。
- ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法中,獲取并篩選有效的 @Configuration類(lèi),實(shí)例化了配置類(lèi)解析器ConfigurationClassParser,調(diào)用其parse方法,解析配置類(lèi)(ScanConfig.class)。
- ConfigurationClassParser#parse包含處理配置類(lèi)中各種注解,其中就包含@ComponentScan。但解析@ComponentScan,委托給了ComponentScanAnnotationParser類(lèi),調(diào)用其parse方法.
- ComponentScanAnnotationParser#parse實(shí)例化了類(lèi)路徑 Bean 定義掃描器ClassPathBeanDefinitionScanner,ClassPathBeanDefinitionScanner加載掃描配置后調(diào)用doScan掃描組件。

相關(guān)文章
Spring Cloud Zuul路由網(wǎng)關(guān)服務(wù)過(guò)濾實(shí)現(xiàn)代碼
這篇文章主要介紹了Spring Cloud Zuul路由網(wǎng)關(guān)服務(wù)過(guò)濾實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
jtds1.1連接sqlserver2000測(cè)試示例
這篇文章主要介紹了jtds1.1連接sqlserver2000測(cè)試示例,需要的朋友可以參考下2014-02-02
Java Code Cache滿(mǎn)導(dǎo)致應(yīng)用性能降低問(wèn)題解決
這篇文章主要介紹了Java Code Cache滿(mǎn)導(dǎo)致應(yīng)用性能降低問(wèn)題解決,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
Java的validation參數(shù)校驗(yàn)代碼實(shí)例
這篇文章主要介紹了Java的validation參數(shù)校驗(yàn)代碼實(shí)例,Validation參數(shù)校驗(yàn)是指在程序運(yùn)行中對(duì)傳進(jìn)來(lái)的參數(shù)進(jìn)行合法性檢查,以保證程序的正確性和安全性,需要的朋友可以參考下2023-10-10
詳解Spring-boot中讀取config配置文件的兩種方式
這篇文章主要介紹了詳解Spring-boot中讀取config配置文件的兩種方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10

