springboot自动装配之@ComponentScan使用方式

 更新时间:2025年01月23日 15:07:16   作者:qq_39093474  
@componentScan注解用于扫描指定路径下的组件,并自动将它们注册为Spring Bean,该注解支持多种过滤规则,可以自定义扫描过滤规则,Spring Boot通过ConfigurationClassPostProcessor处理@ComponentScan注解,并在启动时创建和注册BeanDefinition对象

1.@ComponentScan注解作用

@ComponentScan用于类或接口上主要是指定扫描路径,spring会把指定路径下带有指定注解的类自动装配到bean容器里。

会被自动装配的注解包括@Controller@Service@Component@Repository等等。

ComponentScan注解相对应的XML配置就是<context:component-scan/>, 根据指定的配置自动扫描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有如下常用属性:

  • basePackagesvalue:指定要扫描的路径(package),如果为空则以@ComponentScan注解的类所在的包为基本的扫描路径。
  • basePackageClasses:指定具体扫描的类。
  • includeFilters:指定满足Filter条件的类。
  • excludeFilters:指定排除Filter条件的类。
  • useDefaultFilters=true/false:指定是否需要使用Spring默认的扫描规则:被@Component, @Repository, @Service, @Controller或者已经声明过@Component自定义注解标记的组件;

在过滤规则Filter中:

FilterType:指定过滤规则,支持的过滤规则有:

  • ANNOTATION:按照注解规则,过滤被指定注解标记的类(默认);
  • ASSIGNABLE_TYPE:按照给定的类型;
  • ASPECTJ:按照ASPECTJ表达式;
  • REGEX:按照正则表达式;
  • CUSTOM:自定义规则,自定义的Filter需要实现TypeFilter接口;

value和classes:指定在该规则下过滤的表达式;

@ComponentScan的常见的配置如下:

@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

3. @ComponentScan过滤规则说明

规则表达式说明:

  • 1.扫描指定类文件
@ComponentScan(basePackageClasses = Person.class)
  • 2.扫描指定包,使用默认扫描规则,即被@Component, @Repository, @Service, @Controller或者已经声明过@Component自定义注解标记的组件;
@ComponentScan(value = "com.example")
  • 3.扫描指定包,加载被@Component注解标记的组件和默认规则的扫描(因为useDefaultFilters默认为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标记的组件
@ComponentScan(value = "com.example", excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = Component.class) })
  • 6.扫描指定包,自定义过滤规则
@ComponentScan(value = "com.example", includeFilters = { @Filter(type = FilterType.CUSTOM, value = MtyTypeFilter.class) }, useDefaultFilters = true)

4. 自定义扫描过滤规则

用户自定义扫描过滤规则,需要实现org.springframework.core.type.filter.TypeFilter接口。

//1.自定义类实现TypeFilter接口并重写match()方法
public class MtyTypeFilter implements TypeFilter {
    /**
     *
     * @param metadataReader:读取到当前正在扫描的类的信息
     * @param metadataReaderFactory:可以获取到其他任何类的信息
     * @return
     * @throws IOException
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        System.out.println("========MtyTypeFilter===========");
        //获取当前类的注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        System.out.println("annotationMetadata: "+annotationMetadata);
        //输出结果:annotationMetadata: com.example.test.bean.Color

        //获取当前正在扫描的类的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        System.out.println("classMetadata: "+classMetadata);
        //输出结果: classMetadata: com.example.test.bean.Color

        //获取当前类资源(类的路径)
        Resource resource = metadataReader.getResource();
        System.out.println("resource: "+resource);
        //输出结果:resource: file [D:\idea\demo-02\target\classes\com\example\test\bean\Color.class]


        //获取类名
        String className = classMetadata.getClassName();
        System.out.println("className: "+className);
        //输出结果: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作为数组使用
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创建bean对象的基本流程是先创建对应的BeanDefinition对象,然后在基于BeanDefinition对象来创建Bean对象,SpringBoot也是如此,只不过通过注解创建BeanDefinition对象的时机和解析方式不同而已。

SpringBoot是通过ConfigurationClassPostProcessor这个BeanFactoryPostProcessor类来处理。

本演示的demo涉及到4个演示类,分别是:

  1. 带有@SpringBootApplication注解的启动类Demo02Application。
  2. 带有@RestController注解的类HelloController。
  3. 带有@Configuration注解且有通过@Bean注解来创建addInterceptors的方法的MyMvcConfig类。
  4. Account实体类无任何注解。

本文的最后会贴出所有代码。先从启动类为入口,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);

一路断点到核心方法

SpringApplication.ConfigurableApplicationContext run(String... args)

run方法干了两件事:

  • 创建SpringApplication对象
  • 利用创建好的SpringApplication对象调用run方法
 public ConfigurableApplicationContext run(String... args) {
        long startTime = System.nanoTime();
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
        ConfigurableApplicationContext context = null;
        this.configureHeadlessProperty();
        //初始化监听器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        //发布ApplicationStartingEven
        listeners.starting(bootstrapContext, this.mainApplicationClass);

        try {
        	 //装配参数和环境
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //发布ApplicationEnvironmentPreparedEvent
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            //创建ApplicationContext,并装配
            context = this.createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            //发布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);
            }
			//发布ApplicationStartedEven
            listeners.started(context, timeTakenToStartup);
            //执行Spring中@Bean下的一些操作,如静态方法
            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);
        }
    }

重点方法一:本方法法实现的重点功能:

  1. demoweb工程,springboot通过反射创建上下文context:AnnotationConfigServletWebServerApplicationContext
  2. 在构建context的无参构造方法中构建成员变量reader=new AnnotatedBeanDefinitionReader(this),在AnnotatedBeanDefinitionReader的无参构造方法中会beanFactory对象,并向beanFactory中注册5个BeanDefinition对象,重点关注ConfigurationClassPostProcessor。
context = this.createApplicationContext();

重点方法二:本方法实现的重点功能本方法会构建启动类Demo02Application对应的BeanDefinition对象,并注册到beanFactory中,此时的context对象可见下图

this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

重点方法三:本方法实现的重点功能该方法实际调用applicationContext的refresh方法,本文后面只会分析ConfigurationClassPostProcessor对象的创建和postProcessBeanDefinitionRegistry方法的执行

    this.refreshContext(context);
    this.afterRefresh(context, applicationArguments);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

代码执行后的截图如下:

ConfigurationClassPostProcessor实现BeanFactoryPostProcessor,关于BeanFactoryPostProcessor扩展接口的作用在此处不做介绍。

ConfigurationClassPostProcessor对象的创建和方法执行的断点如下:

this.refreshContext(context);–> AbstractApplicationContext.refresh() --> invokeBeanFactoryPostProcessors() -->PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()->invokeBeanDefinitionRegistryPostProcessors()

下面重点看ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry()方法如何处理@ComponentScan注解:

同过源代码发现最终是由ConfigurationClassParser的解析类来处理,继续查看ConfigurationClassParser.doProcessConfigurationClass

原来在这里对@ComponentScan注解做了判断,上面一段代码做了核心的几件事:

  1. 扫描@ComponentScan注解包下面的所有的可自动装备类,生成BeanDefinition对象,并注册到beanFactory对象中。
  2. 通过DeferredImportSelectorHandler处理@EnableAutoConfiguration注解,后续会有专文介绍。
  3. 将带有@Configuration 注解的类解析成ConfigurationClass对象并缓存,后面创建@Bean注解的Bean对象所对应的BeanDefinition时会用到到此为止MyFilter2对应的BeanDefinition已创建完毕。
  4. 如下图:

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • VSCode 配置 Spring Boot 项目开发环境的全过程

    VSCode 配置 Spring Boot 项目开发环境的全过程

    两三年前曾经试过配置Java环境, 存在不少问题作罢. 最近搜了下相关的文章, 感觉VSCode对Java项目的支持比三年前完善了不少. 今天实际配置了一下环境, 把自己常用的功能过了一遍, 基本能跑通开发流程, 做个笔记,需要的朋友可以参考下
    2024-03-03
  • maven profile实现多环境配置的示例

    maven profile实现多环境配置的示例

    这篇文章主要介绍了maven profile实现多环境配置的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • java用扑克牌计算24点

    java用扑克牌计算24点

    这篇文章主要为大家详细介绍了java实现24点扑克牌游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-03-03
  • Java实现文件批量重命名具体实例

    Java实现文件批量重命名具体实例

    这篇文章主要介绍了Java实现文件批量重命名具体实例,需要的朋友可以参考下
    2014-02-02
  • java-RGB调色面板的实现(事件监听器之匿名内部类)

    java-RGB调色面板的实现(事件监听器之匿名内部类)

    这篇文章主要介绍了java-RGB调色面板的实现(事件监听器之匿名内部类),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • springboot整合quartz项目使用案例

    springboot整合quartz项目使用案例

    quartz是一个定时调度的框架,就目前市场上来说,其实有比quartz更优秀的一些定时调度框架,不但性能比quartz好,学习成本更低,而且还提供可视化操作定时任务,这篇文章主要介绍了springboot整合quartz项目使用(含完整代码),需要的朋友可以参考下
    2023-05-05
  • Java 读取PDF中的文本和图片的方法

    Java 读取PDF中的文本和图片的方法

    本文将介绍通过Java程序来读取PDF文档中的文本和图片的方法。分别调用方法extractText()和extractImages()来读取,需要的朋友可以参考下
    2019-07-07
  • 解读为何java中的boolean类型是32位的

    解读为何java中的boolean类型是32位的

    这篇文章主要介绍了为何java中的boolean类型是32位的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • Spring解决泛型擦除的思路详解

    Spring解决泛型擦除的思路详解

    这篇文章主要介绍了Spring解决泛型擦除的思路,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-01-01
  • Flink实现特定统计的归约聚合reduce操作

    Flink实现特定统计的归约聚合reduce操作

    这篇文章主要介绍了Flink实现特定统计的归约聚合reduce操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-02-02

最新评论