spring?boot自動裝配之@ComponentScan注解用法詳解
1.@ComponentScan注解作用
@ComponentScan用于類或接口上主要是指定掃描路徑,spring會把指定路徑下帶有指定注解的類自動裝配到bean容器里。會被自動裝配的注解包括@Controller、@Service、@Component、@Repository等等。與ComponentScan注解相對應(yīng)的XML配置就是<context:component-scan/>, 根據(jù)指定的配置自動掃描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
可以一次聲明多個@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對象的基本流程是先創(chuàng)建對應(yīng)的BeanDefinition對象,然后在基于BeanDefinition對象來創(chuàng)建Bean對象,SpringBoot也是如此,只不過通過注解創(chuàng)建BeanDefinition對象的時機(jī)和解析方式不同而已。SpringBoot是通過ConfigurationClassPostProcessor這個BeanFactoryPostProcessor類來處理。
本演示的demo涉及到4個演示類,分別是:
- 帶有@SpringBootApplication注解的啟動類Demo02Application。
- 帶有@RestController注解的類HelloController。
- 帶有@Configuration注解且有通過@Bean注解來創(chuàng)建addInterceptors的方法的MyMvcConfig類。
- Account實(shí)體類無任何注解。
本文的最后會貼出所有代碼。 先從啟動類為入口,SpringBoot啟動類如下:
@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對象
- 利用創(chuàng)建好的SpringApplication對象調(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)造方法中會beanFactory對象,并向beanFactory中注冊5個BeanDefinition對象,重點(diǎn)關(guān)注ConfigurationClassPostProcessor。
context = this.createApplicationContext();
重點(diǎn)方法二:本方法實(shí)現(xiàn)的重點(diǎn)功能
本方法會構(gòu)建啟動類Demo02Application對應(yīng)的BeanDefinition對象,并注冊到beanFactory中,此時的context對象可見下圖
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
重點(diǎn)方法三:本方法實(shí)現(xiàn)的重點(diǎn)功能
該方法實(shí)際調(diào)用applicationContext的refresh方法,代碼分析詳見我的另一篇博客,本文后面只會分析ConfigurationClassPostProcessor對象的創(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ò)展接口的作用在《spring初始化源碼淺析之關(guān)鍵類和擴(kuò)展接口》一文中有詳細(xì)介紹。
ConfigurationClassPostProcessor對象的創(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

原來在這里對@ComponentScan注解做了判斷,上面一段代碼做了核心的幾件事:
- 掃描@ComponentScan注解包下面的所有的可自動裝備類,生成BeanDefinition對象,并注冊到beanFactory對象中。
- 通過DeferredImportSelectorHandler處理@EnableAutoConfiguration注解,后續(xù)會有專文介紹。
- 將帶有@Configuration 注解的類解析成ConfigurationClass對象并緩存,后面創(chuàng)建@Bean注解的Bean對象所對應(yīng)的BeanDefinition時會用到
到此為止MyFilter2對應(yīng)的BeanDefinition已創(chuàng)建完畢,如下圖:

總結(jié)
到此這篇關(guān)于spring boot自動裝配之@ComponentScan注解用法詳解的文章就介紹到這了,更多相關(guān)@ComponentScan注解用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java下載遠(yuǎn)程服務(wù)器文件到本地(基于http協(xié)議和ssh2協(xié)議)
這篇文章主要介紹了Java下載遠(yuǎn)程服務(wù)器文件到本地的方法(基于http協(xié)議和ssh2協(xié)議),幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2021-01-01
詳解Java的Hibernate框架中的set映射集與SortedSet映射
這篇文章主要介紹了詳解Java的Hibernate框架中的set映射集與SortedSet映射,Hibernate是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-12-12
MyBatis-Plus動態(tài)表名使用selectPage方法不生效問題解析與解決方案
MyBatis-Plus是MyBatis的增強(qiáng)工具,動態(tài)表名是MyBatis-Plus的一個重要功能之一,一些開發(fā)者在使用selectPage方法時可能會遇到動態(tài)表名不生效的問題,本文將深入分析這個問題的原因,并提供相應(yīng)的解決方案,需要的朋友可以參考下2023-12-12
springboot?security使用jwt認(rèn)證方式
這篇文章主要介紹了springboot?security使用jwt認(rèn)證方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-04-04
java中JVM中如何存取數(shù)據(jù)和相關(guān)信息詳解
這篇文章主要介紹了JVM中如何存取數(shù)據(jù)和相關(guān)信息詳解,Java源代碼文件(.java后綴)會被Java編譯器編譯為字節(jié)碼文件,然后由JVM中的類加載器加載各個類的字節(jié)碼文件,加載完畢之后,交由JVM執(zhí)行引擎執(zhí)行。JVM中怎么存取數(shù)據(jù)和相關(guān)信息呢?,需要的朋友可以參考下2019-06-06
SWT(JFace)體驗(yàn)之FillLayout布局
FillLayout是非常簡單的一種布局方式,它會以同樣大小對父組件中的子組件進(jìn)行布局,這些子組件將以一行或一列的形式排列。2009-06-06
idea 實(shí)現(xiàn)git rebase操作應(yīng)用場景
本文結(jié)合idea工具進(jìn)行rebase的各種場景的操作,借助工具更能直觀地觀察到分支之間地操作差異,方便我們理解rebase的各種操作以及場景的使用,對idea git rebase操作知識感興趣的朋友一起看看吧2024-01-01

