springboot自動(dòng)裝配之@ComponentScan使用方式
1.@ComponentScan注解作用
@ComponentScan
用于類或接口
上主要是指定掃描路徑
,spring會(huì)把指定路徑下帶有指定注解的類
自動(dòng)裝配到bean容器里。
會(huì)被自動(dòng)裝配的注解包括@Controller
、@Service
、@Component
、@Repository
等等。
與ComponentScan
注解相對(duì)應(yīng)的XML配置就是<context:component-scan/>
, 根據(jù)指定的配置自動(dòng)掃描package
,將符合條件的組件加入到IOC容器中;
XML的配置方式如下:
<context:component-scan base-package="com.example.test" use-default-filters="false"> <context:exclude-filter type="custom" expression="com.example.test.filter.MtyTypeFilter" /> </context:component-scan>
2. @ComponentScan注解屬性
@ComponentScan有如下常用屬性:
basePackages
和value
:指定要掃描的路徑(package
),如果為空則以@ComponentScan
注解的類所在的包為基本的掃描路徑。basePackageClasses
:指定具體掃描的類。includeFilters
:指定滿足Filter
條件的類。excludeFilters
:指定排除Filter
條件的類。useDefaultFilters=true/false
:指定是否需要使用Spring
默認(rèn)的掃描規(guī)則:被@Component, @Repository, @Service, @Controller
或者已經(jīng)聲明過@Component
自定義注解標(biāo)記的組件;
在過濾規(guī)則Filter中:
FilterType:指定過濾規(guī)則,支持的過濾規(guī)則有:
ANNOTATION
:按照注解規(guī)則,過濾被指定注解標(biāo)記的類(默認(rèn));ASSIGNABLE_TYPE
:按照給定的類型;ASPECTJ
:按照ASPECTJ表達(dá)式;REGEX
:按照正則表達(dá)式;CUSTOM
:自定義規(guī)則,自定義的Filter需要實(shí)現(xiàn)TypeFilter接口;
value和classes:指定在該規(guī)則下過濾的表達(dá)式;
@ComponentScan的常見的配置如下:
@ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} )
3. @ComponentScan過濾規(guī)則說明
規(guī)則表達(dá)式說明:
- 1.掃描指定類文件
@ComponentScan(basePackageClasses = Person.class)
- 2.掃描指定包,使用默認(rèn)掃描規(guī)則,即被@Component, @Repository, @Service, @Controller或者已經(jīng)聲明過@Component自定義注解標(biāo)記的組件;
@ComponentScan(value = "com.example")
- 3.掃描指定包,加載被@Component注解標(biāo)記的組件和默認(rèn)規(guī)則的掃描(因?yàn)閡seDefaultFilters默認(rèn)為true)
@ComponentScan(value = "com.example", includeFilters = { @Filter(type = FilterType.ANNOTATION, value = Component.class) })
- 4.掃描指定包,只加載Person類型的組件
@ComponentScan(value = "com.example", includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = Person.class) }, useDefaultFilters = false)
- 5.掃描指定包,過濾掉被@Component標(biāo)記的組件
@ComponentScan(value = "com.example", excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = Component.class) })
- 6.掃描指定包,自定義過濾規(guī)則
@ComponentScan(value = "com.example", includeFilters = { @Filter(type = FilterType.CUSTOM, value = MtyTypeFilter.class) }, useDefaultFilters = true)
4. 自定義掃描過濾規(guī)則
用戶自定義掃描過濾規(guī)則,需要實(shí)現(xiàn)org.springframework.core.type.filter.TypeFilter
接口。
//1.自定義類實(shí)現(xiàn)TypeFilter接口并重寫match()方法 public class MtyTypeFilter implements TypeFilter { /** * * @param metadataReader:讀取到當(dāng)前正在掃描的類的信息 * @param metadataReaderFactory:可以獲取到其他任何類的信息 * @return * @throws IOException */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { System.out.println("========MtyTypeFilter==========="); //獲取當(dāng)前類的注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); System.out.println("annotationMetadata: "+annotationMetadata); //輸出結(jié)果:annotationMetadata: com.example.test.bean.Color //獲取當(dāng)前正在掃描的類的類信息 ClassMetadata classMetadata = metadataReader.getClassMetadata(); System.out.println("classMetadata: "+classMetadata); //輸出結(jié)果: classMetadata: com.example.test.bean.Color //獲取當(dāng)前類資源(類的路徑) Resource resource = metadataReader.getResource(); System.out.println("resource: "+resource); //輸出結(jié)果:resource: file [D:\idea\demo-02\target\classes\com\example\test\bean\Color.class] //獲取類名 String className = classMetadata.getClassName(); System.out.println("className: "+className); //輸出結(jié)果:className: com.example.test.bean.Color Class<?> forName = null; try { forName = Class.forName(className); if (Color.class.isAssignableFrom(forName)) { // 如果是Color的子類,就加載到IOC容器 return true; } } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println("========MtyTypeFilter==========="); return false; } }
5. @ComponentScans
可以一次聲明多個(gè)@ComponentScan
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Repeatable(ComponentScans.class) //指定ComponentScan可以被ComponentScans作為數(shù)組使用 public @interface ComponentScan { } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface ComponentScans { ComponentScan[] value(); }
@ComponentScans(value = { @ComponentScan(value = "com.example.test"), @ComponentScan(value = "com.example.test", includeFilters = { @Filter(type = FilterType.CUSTOM, value = MtyTypeFilter.class) }) }) public class MainConfig { @Bean(name = "pers", initMethod = "init", destroyMethod = "destory") public Person person() { return new Person(); } }
6. spring boot處理@ComponentScan源碼分析
spring
創(chuàng)建bean
對(duì)象的基本流程是先創(chuàng)建對(duì)應(yīng)的BeanDefinition
對(duì)象,然后在基于BeanDefinition
對(duì)象來創(chuàng)建Bean
對(duì)象,SpringBoot
也是如此,只不過通過注解創(chuàng)建BeanDefinition對(duì)象的時(shí)機(jī)和解析方式不同而已。
SpringBoot
是通過ConfigurationClassPostProcessor
這個(gè)BeanFactoryPostProcessor
類來處理。
本演示的demo涉及到4個(gè)演示類,分別是:
- 帶有@SpringBootApplication注解的啟動(dòng)類Demo02Application。
- 帶有@RestController注解的類HelloController。
- 帶有@Configuration注解且有通過@Bean注解來創(chuàng)建addInterceptors的方法的MyMvcConfig類。
- Account實(shí)體類無任何注解。
本文的最后會(huì)貼出所有代碼。先從啟動(dòng)類為入口,SpringBoot啟動(dòng)類如下:
@SpringBootApplication public class Demo02Application { public static void main(String[] args) { //1、返回我們IOC容器 ConfigurableApplicationContext run = SpringApplication.run(Demo02Application.class, args); } }
從:
SpringApplication.run(Demo02Application.class, args);
一路斷點(diǎn)到核心方法
SpringApplication.ConfigurableApplicationContext run(String... args)
run方法干了兩件事:
- 創(chuàng)建SpringApplication對(duì)象
- 利用創(chuàng)建好的SpringApplication對(duì)象調(diào)用run方法
public ConfigurableApplicationContext run(String... args) { long startTime = System.nanoTime(); DefaultBootstrapContext bootstrapContext = this.createBootstrapContext(); ConfigurableApplicationContext context = null; this.configureHeadlessProperty(); //初始化監(jiān)聽器 SpringApplicationRunListeners listeners = this.getRunListeners(args); //發(fā)布ApplicationStartingEven listeners.starting(bootstrapContext, this.mainApplicationClass); try { //裝配參數(shù)和環(huán)境 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //發(fā)布ApplicationEnvironmentPreparedEvent ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments); this.configureIgnoreBeanInfo(environment); Banner printedBanner = this.printBanner(environment); //創(chuàng)建ApplicationContext,并裝配 context = this.createApplicationContext(); context.setApplicationStartup(this.applicationStartup); //發(fā)布ApplicationPreparedEvent this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); this.refreshContext(context); this.afterRefresh(context, applicationArguments); Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup); } //發(fā)布ApplicationStartedEven listeners.started(context, timeTakenToStartup); //執(zhí)行Spring中@Bean下的一些操作,如靜態(tài)方法 this.callRunners(context, applicationArguments); } catch (Throwable var12) { this.handleRunFailure(context, var12, listeners); throw new IllegalStateException(var12); } try { Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime); listeners.ready(context, timeTakenToReady); return context; } catch (Throwable var11) { this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null); throw new IllegalStateException(var11); } }
重點(diǎn)方法一:本方法法實(shí)現(xiàn)的重點(diǎn)功能:
- 本
demo
是web
工程,springboot
通過反射創(chuàng)建上下文context:AnnotationConfigServletWebServerApplicationContext
類 - 在構(gòu)建context的無參構(gòu)造方法中構(gòu)建成員變量reader=new AnnotatedBeanDefinitionReader(this),在AnnotatedBeanDefinitionReader的無參構(gòu)造方法中會(huì)beanFactory對(duì)象,并向beanFactory中注冊(cè)5個(gè)BeanDefinition對(duì)象,重點(diǎn)關(guān)注ConfigurationClassPostProcessor。
context = this.createApplicationContext();
重點(diǎn)方法二:本方法實(shí)現(xiàn)的重點(diǎn)功能本方法會(huì)構(gòu)建啟動(dòng)類Demo02Application對(duì)應(yīng)的BeanDefinition對(duì)象,并注冊(cè)到beanFactory中,此時(shí)的context對(duì)象可見下圖
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
重點(diǎn)方法三:本方法實(shí)現(xiàn)的重點(diǎn)功能該方法實(shí)際調(diào)用applicationContext的refresh方法,本文后面只會(huì)分析ConfigurationClassPostProcessor對(duì)象的創(chuàng)建和postProcessBeanDefinitionRegistry方法的執(zhí)行
this.refreshContext(context); this.afterRefresh(context, applicationArguments);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
代碼執(zhí)行后的截圖如下:
ConfigurationClassPostProcessor實(shí)現(xiàn)BeanFactoryPostProcessor,關(guān)于BeanFactoryPostProcessor擴(kuò)展接口的作用在此處不做介紹。
ConfigurationClassPostProcessor對(duì)象的創(chuàng)建和方法執(zhí)行的斷點(diǎn)如下:
this.refreshContext(context);–> AbstractApplicationContext.refresh() --> invokeBeanFactoryPostProcessors() -->PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()->invokeBeanDefinitionRegistryPostProcessors()
下面重點(diǎn)看ConfigurationClassPostProcessor類的postProcessBeanDefinitionRegistry()方法如何處理@ComponentScan注解:
同過源代碼發(fā)現(xiàn)最終是由ConfigurationClassParser的解析類來處理,繼續(xù)查看ConfigurationClassParser.doProcessConfigurationClass
原來在這里對(duì)@ComponentScan注解做了判斷,上面一段代碼做了核心的幾件事:
- 掃描@ComponentScan注解包下面的所有的可自動(dòng)裝備類,生成BeanDefinition對(duì)象,并注冊(cè)到beanFactory對(duì)象中。
- 通過DeferredImportSelectorHandler處理@EnableAutoConfiguration注解,后續(xù)會(huì)有專文介紹。
- 將帶有@Configuration 注解的類解析成ConfigurationClass對(duì)象并緩存,后面創(chuàng)建@Bean注解的Bean對(duì)象所對(duì)應(yīng)的BeanDefinition時(shí)會(huì)用到到此為止MyFilter2對(duì)應(yīng)的BeanDefinition已創(chuàng)建完畢。
- 如下圖:
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
VSCode?配置?Spring?Boot?項(xiàng)目開發(fā)環(huán)境的全過程
兩三年前曾經(jīng)試過配置Java環(huán)境, 存在不少問題作罷. 最近搜了下相關(guān)的文章, 感覺VSCode對(duì)Java項(xiàng)目的支持比三年前完善了不少. 今天實(shí)際配置了一下環(huán)境, 把自己常用的功能過了一遍, 基本能跑通開發(fā)流程, 做個(gè)筆記,需要的朋友可以參考下2024-03-03maven profile實(shí)現(xiàn)多環(huán)境配置的示例
這篇文章主要介紹了maven profile實(shí)現(xiàn)多環(huán)境配置的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Java實(shí)現(xiàn)文件批量重命名具體實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)文件批量重命名具體實(shí)例,需要的朋友可以參考下2014-02-02java-RGB調(diào)色面板的實(shí)現(xiàn)(事件監(jiān)聽器之匿名內(nèi)部類)
這篇文章主要介紹了java-RGB調(diào)色面板的實(shí)現(xiàn)(事件監(jiān)聽器之匿名內(nèi)部類),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11springboot整合quartz項(xiàng)目使用案例
quartz是一個(gè)定時(shí)調(diào)度的框架,就目前市場(chǎng)上來說,其實(shí)有比quartz更優(yōu)秀的一些定時(shí)調(diào)度框架,不但性能比quartz好,學(xué)習(xí)成本更低,而且還提供可視化操作定時(shí)任務(wù),這篇文章主要介紹了springboot整合quartz項(xiàng)目使用(含完整代碼),需要的朋友可以參考下2023-05-05Flink實(shí)現(xiàn)特定統(tǒng)計(jì)的歸約聚合reduce操作
這篇文章主要介紹了Flink實(shí)現(xiàn)特定統(tǒng)計(jì)的歸約聚合reduce操作,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-02-02