Spring?IOC中的組件掃描
版本 Spring Framework 6.0.9?
1. 前言
通過自動(dòng)掃描,Spring 會自動(dòng)從掃描指定的包及其子包下的所有類,并根據(jù)類上的特定注解將該類裝配到容器中,而無需在 XML 配置文件或 Java 配置類中逐一聲明每一個(gè) Bean。
支持的注解
Spring 支持一系列注解,用于標(biāo)記哪些類應(yīng)被自動(dòng)掃描并作為 Bean 管理。這些注解通常包含:
- @Component:基礎(chǔ)注解,標(biāo)記一個(gè)類作為 Spring 組件。所有其他特殊用途的組件注解都繼承自此注解。
- @Repository:用于標(biāo)注 DAO(Data Access Object)類,除了具備 @Component 的功能外,還為數(shù)據(jù)訪問異常提供了特殊的翻譯機(jī)制。
- @Service:用于標(biāo)注業(yè)務(wù)層(Service)類,強(qiáng)調(diào)這是一個(gè)業(yè)務(wù)相關(guān)的組件。
- @Controller:用于標(biāo)注 MVC 架構(gòu)中的控制器類,通常在 Spring MVC 中使用,與 Spring Web 相關(guān)的請求處理邏輯相關(guān)聯(lián)。
- @Configuration:用于標(biāo)注配置類,這類類通常包含 @Bean 方法,用于定義其他 Bean。
- @RestController:結(jié)合了 @Controller 和 @ResponseBody,適用于構(gòu)建 RESTful Web 服務(wù)的控制器。
例子相關(guān)實(shí)體類
控制層(例子中沒引入spring-web包,注解先使用@Component替代)
服務(wù)層
數(shù)據(jù)訪問層
2. 基于xml
2.1 使用
在 Spring 的XML配置文件中,通過 <context:component-scan> 標(biāo)簽開啟自動(dòng)掃描功能。我們可以配置 base-package 指定掃描路徑。
輸出信息說明每個(gè)實(shí)體類都已加載到容器。
<context:component-scan> 其他配置元素:
- resource-pattern:可選屬性,用于指定在掃描包路徑下應(yīng)匹配的資源模式。默認(rèn)值為 “**/*.class”,即掃描所有 .class 文件。可以根據(jù)需要修改此模式,例如,僅掃描特定目錄下的類。
- use-default-filters:布爾屬性,默認(rèn)值為 true。決定是否啟用默認(rèn)的過濾規(guī)則,即查找?guī)в?@Component、@Repository、@Service、@Controller、@Configuration 等注解的類。如果設(shè)置為 false,則需要手動(dòng)配置 和 來指定掃描規(guī)則。
- scope-resolver:指定一個(gè)自定義的 ScopeMetadataResolver 實(shí)現(xiàn)類,用于確定掃描到的 Bean 的作用域。默認(rèn)情況下,Spring 使用 AnnotationScopeMetadataResolver,根據(jù)類上的 @Scope 注解來確定作用域。
- scoped-proxy:指定是否為掃描到的帶有作用域注解的 Bean 創(chuàng)建代理。可能的值包括:
- no(默認(rèn)):不創(chuàng)建代理。
- interfaces:為實(shí)現(xiàn)了接口的 Bean 創(chuàng)建 JDK 動(dòng)態(tài)代理。
- targetClass:為未實(shí)現(xiàn)接口或需要保留原始類類型的 Bean 創(chuàng)建 CGLIB 代理。
- name-generator:指定一個(gè)自定義的 BeanNameGenerator 實(shí)現(xiàn)類,用于生成掃描到的 Bean 的名稱。默認(rèn)使用 AnnotationBeanNameGenerator,根據(jù)類上的 @Component 等注解的 value 屬性或類名生成 Bean 名稱。
- include-filter 和 exclude-filter:這兩個(gè)元素屬于子標(biāo)簽,用于指定更細(xì)粒度的掃描規(guī)則??梢园醋⒔忸愋汀⒆⒔鈱傩?、全類名模式等進(jìn)行包含或排除。
2.2 掃描原理
- 當(dāng)我們使用ClassPathXmlApplicationContext作為IOC容器,在refresh方法的obtainFreshBeanFactory階段會創(chuàng)建一個(gè)bean工廠DefaultListableBeanFactory。
- 當(dāng)前上下文調(diào)用loadBeanDefinitions方法,根據(jù)容器的配置文件加載bean定義。
- 配置文件中通過 <context:component-scan> 標(biāo)簽開啟自動(dòng)掃描功能,屬于其他命名空間,托管給 ComponentScanBeanDefinitionParser處理。
- ComponentScanBeanDefinitionParser#parse方法中創(chuàng)建一個(gè)ClassPathBeanDefinitionScanner對象,調(diào)用其scan方法掃描組件并加載到bean工廠中。
其他命名空間
Spring框架除了核心的XML命名空間外,還提供了多個(gè)擴(kuò)展命名空間(除beans以外的命名空間),以支持特定的功能模塊和簡化配置。
- http://www.springframework.org/schema/beans:核心命名空間,這是最基礎(chǔ)且最常用的命名空間,用于定義bean、設(shè)置屬性、注入依賴、配置構(gòu)造函數(shù)參數(shù)、初始化方法、銷毀方法、自動(dòng)裝配等基本IoC容器功能。http://www.springframework.org/schema/context:
- context命名空間,提供了對Java配置類、自動(dòng)掃描、注解驅(qū)動(dòng)的bean定義、屬性占位符替換、資源加載等上下文相關(guān)的高級功能的支持。通過此命名空間,可以簡化基于注解的配置,如@Component、@Autowired等。
在實(shí)例化XmlReaderContext時(shí),DefaultBeanDefinitionDocumentReader會創(chuàng)建一個(gè)命名空間解析器DefaultNamespaceHandlerResolver,緩存到XmlReaderContext,當(dāng)Reader解析配置文件時(shí)發(fā)現(xiàn)存在其他命名空間時(shí),通過DefaultNamespaceHandlerResolver加載 META-INF/spring.handlers 路徑下的其他命名空間處理器,獲取對應(yīng)的處理器處理。
XmlReaderContext 是Spring框架中用于處理XML配置文件解析過程中的上下文對象,為DefaultBeanDefinitionDocumentReader在解析和處理XML配置文件時(shí)提供了必要的環(huán)境支持。
context命名空間處理器(ComponentScanBeanDefinitionParser)
- 獲取基礎(chǔ)包名basePackages。
- 創(chuàng)建類路徑 Bean 定義掃描程序ClassPathBeanDefinitionScanner。
- 遍歷掃描包名,查找被@Component及其派生注解修飾的類。將它們封裝為BeanDefinitionHolder對象。
- 往bean工廠加載特定注解后置處理器的bean定義(internalConfigurationAnnotationProcessor、internalAutowiredAnnotationProcess、internalCommonAnnotationProcessor、internalEventListenerProcessor、internalEventListenerFactory),觸發(fā)組件注冊事件.
獲取基礎(chǔ)包名basePackages
- element.getAttribute(BASE_PACKAGE_ATTRIBUTE)方法從對象中獲取base-package的屬性值。
- parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage)方法解析占位符,獲取解析值。
- StringUtils.tokenizeToStringArray(basePackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)方法將基礎(chǔ)包名按逗號、分號或空格等常見分隔符拆分為一個(gè)字符串?dāng)?shù)組basePackages。
創(chuàng)建組件掃描器
ComponentScanBeanDefinitionParser#configureScanner方法通過對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。
另外通過ClassPathBeanDefinitionScanner構(gòu)造函數(shù)實(shí)例化時(shí),會創(chuàng)建一個(gè)Component類型的注解類型過濾器AnnotationTypeFilter添加到includeFilters屬性中,用于將類與@Component進(jìn)行匹配。
掃描組件
ClassPathBeanDefinitionScanner#doScan掃描指定的basePackages包及其子包,查找并處理符合要求的bean定義,最終將它們封裝為BeanDefinitionHolder對象并返回,bean定義類型是ScannedGenericBeanDefinition。
掃描核心方法ClassPathScanningCandidateComponentProvider#findCandidateComponents用于在指定的basePackage包及其子包下查找符合要求的候選bean組件(BeanDefinition),支持索引查找(效率更高)或按傳統(tǒng)方式查找(獲取指定包下所有class對象篩選)。
我們只看傳統(tǒng)方式查找方式,ClassPathScanningCandidateComponentProvider#scanCandidateComponents方法用于在給定的basePackage及其子包下通過類路徑掃描機(jī)制查找候選bean組件(BeanDefinition).
ClassPathScanningCandidateComponentProvider#isCandidateComponent方法用于判斷給定的MetadataReader所代表的類是否滿足候選bean組件的條件,在不添加其他排除過濾器、包含過濾器或配置,上面創(chuàng)建組件掃描器過程中,已知添加了一個(gè)AnnotationTypeFilter包含過濾器,在此處使用,匹配被@Component注解的類。
觸發(fā)組件注冊事件
ComponentScanBeanDefinitionParser#registerComponents負(fù)責(zé)將一組BeanDefinition注冊到Spring IoC容器中,并處理與之相關(guān)的XML元素及注解配置處理器。實(shí)際上做了兩件事
往bean工廠加載特定注解后置處理器的bean定義觸發(fā)組件注冊事件,一般情況下是EmptyReaderEventListener,空方法。
3. 全注解開發(fā)
AnnotationConfigApplicationContext 是 Spring Framework 中的一個(gè)核心類,用于創(chuàng)建和管理基于 Java 注解的 Spring 應(yīng)用程序上下文。AnnotationConfigApplicationContext 有兩種開啟組件掃描的方式
- 直接指定包路徑
- 通過@ComponentScan注解
3.1 通過指定包路徑開啟掃描
使用
掃描原理
接受一個(gè)字符串?dāng)?shù)組參數(shù) basePackages的有參構(gòu)造邏輯分成三部分:
- this(); 調(diào)用無參構(gòu)造函數(shù)
- scan(basePackages); 執(zhí)行類路徑掃描
- refresh(); 啟動(dòng)應(yīng)用上下文
無參構(gòu)造函數(shù)實(shí)例化組件掃描器 ClassPathBeanDefinitionScanner,用于在類路徑中掃描并注冊基于注解的 Bean 定義(如 @Component、@Service、@Repository、@Controller 等)。
調(diào)用ClassPathBeanDefinitionScanner#scan方法掃描組件,與基于xml掃描邏輯一樣,調(diào)用的是ClassPathBeanDefinitionScanner#scan方法,所以bean定義創(chuàng)建的類型也是ScannedGenericBeanDefinition類。
3.2 通過Java配置類開啟掃描
使用
c
掃描原理
接受一個(gè)類型為 Class<?> 的可變參數(shù)數(shù)組 componentClasses的有參構(gòu)造邏輯分成三部分:
- this(); 調(diào)用無參構(gòu)造函數(shù)
- register(componentClasses); 注冊指定的組件
- refresh(); 啟動(dòng)應(yīng)用上下文
第一部分雖然與接受字符串的有參構(gòu)造(指定掃描路徑)一樣調(diào)用了無參構(gòu)造,但我們這里只關(guān)注 帶注釋的 Bean 定義讀取器AnnotatedBeanDefinitionReader,而不是類路徑 Bean 定義掃描程器ClassPathBeanDefinitionScanner。
實(shí)例化AnnotatedBeanDefinitionReader過程中,會調(diào)用AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)方法,往bean工廠注冊一個(gè)bean定義后置處理器ConfigurationClassPostProcessor。
第二部分注冊Java配置類ScanConfig,實(shí)例化并初始化bean定義類AnnotatedGenericBeanDefinition,并加載到bean工廠中。
第三部分在refresh方法invokeBeanFactoryPostProcessors階段,會調(diào)用在第一部分注冊的ConfigurationClassPostProcessor(在當(dāng)前階段會調(diào)用getBean方法實(shí)例化)后置處理。
ConfigurationClassPostProcessor 是 Spring 框架中一個(gè)重要的 BeanDefinitionRegistryPostProcessor 實(shí)現(xiàn),主要負(fù)責(zé)處理帶有 @Configuration 注解的類以及它們內(nèi)部定義的 @Bean 方法和其他相關(guān)注解。postProcessBeanDefinitionRegistry方法會篩選出有效的@configuration類,調(diào)用配置類解析器ConfigurationClassParser的parse方法解析。
從第二部分知道,ScanConfig生成的bean定義類是AnnotatedGenericBeanDefinition,是AnnotatedBeanDefinition的子類。debug源碼到處理@ComponentScan注解(this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());),說明ConfigurationClassParser處理邏輯委托給componentScanParser,調(diào)用其parse方法處理。
這里的this.componentScanParser是ComponentScanAnnotationParser類,在調(diào)用ConfigurationClassParser構(gòu)造函數(shù)實(shí)例化時(shí)創(chuàng)建。
ComponentScanAnnotationParser#parse方法中的邏輯就很熟悉了,可以對標(biāo)context命名空間處理各個(gè)屬性。在方法中是新建了一個(gè)ClassPathBeanDefinitionScanner類,而不是使用AnnotationConfigApplicationContext無參構(gòu)造中實(shí)例化的ClassPathBeanDefinitionScanner,最后執(zhí)行doscan方法掃描組件。
總結(jié)
總結(jié)一下通過@ComponentScan開啟掃描流程.
- this();
- 在實(shí)例化AnnotatedBeanDefinitionReader過程中,往bean工廠加載了ConfigurationClassPostProcessor的bean定義
- register(componentClasses);
- 往bean工廠加載了包含@Configuration、@ComponentScan注解的ScanConfig配置類 的bean定義。
- refresh();
- 在invokeBeanFactoryPostProcessors階段實(shí)例化了ConfigurationClassPostProcessor,并調(diào)用其postProcessBeanDefinitionRegistry方法處理@Configuration配置類。
- ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法中,獲取并篩選有效的 @Configuration類,實(shí)例化了配置類解析器ConfigurationClassParser,調(diào)用其parse方法,解析配置類(ScanConfig.class)。
- ConfigurationClassParser#parse包含處理配置類中各種注解,其中就包含@ComponentScan。但解析@ComponentScan,委托給了ComponentScanAnnotationParser類,調(diào)用其parse方法.
- ComponentScanAnnotationParser#parse實(shí)例化了類路徑 Bean 定義掃描器ClassPathBeanDefinitionScanner,ClassPathBeanDefinitionScanner加載掃描配置后調(diào)用doScan掃描組件。
相關(guān)文章
Spring Cloud Zuul路由網(wǎng)關(guān)服務(wù)過濾實(shí)現(xiàn)代碼
這篇文章主要介紹了Spring Cloud Zuul路由網(wǎng)關(guān)服務(wù)過濾實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Java Code Cache滿導(dǎo)致應(yīng)用性能降低問題解決
這篇文章主要介紹了Java Code Cache滿導(dǎo)致應(yīng)用性能降低問題解決,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08Java的validation參數(shù)校驗(yàn)代碼實(shí)例
這篇文章主要介紹了Java的validation參數(shù)校驗(yàn)代碼實(shí)例,Validation參數(shù)校驗(yàn)是指在程序運(yùn)行中對傳進(jìn)來的參數(shù)進(jìn)行合法性檢查,以保證程序的正確性和安全性,需要的朋友可以參考下2023-10-10詳解Spring-boot中讀取config配置文件的兩種方式
這篇文章主要介紹了詳解Spring-boot中讀取config配置文件的兩種方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10