• spring boot自动装配之@ComponentScan详解


    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2. @ComponentScan注解属性

    @ComponentScan有如下常用属性:

    • basePackages和value:指定要扫描的路径(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}
    )}
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4. 自定义扫描过滤规则

    1. 用户自定义扫描过滤规则,需要实现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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    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();
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    @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();
    	}
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 重点方法一:本方法法实现的重点功能:
    1. demoweb工程,springboot通过反射创建上下文context:AnnotationConfigServletWebServerApplicationContext
    2. 在构建context的无参构造方法中构建成员变量reader=new AnnotatedBeanDefinitionReader(this),在AnnotatedBeanDefinitionReader的无参构造方法中会beanFactory对象,并向beanFactory中注册5个BeanDefinition对象,重点关注ConfigurationClassPostProcessor。
    context = this.createApplicationContext();
    
    • 1
    • 重点方法二:本方法实现的重点功能
      本方法会构建启动类Demo02Application对应的BeanDefinition对象,并注册到beanFactory中,此时的context对象可见下图
       this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    
    • 1
    • 重点方法三:本方法实现的重点功能
      该方法实际调用applicationContext的refresh方法,代码分析详见我的另一篇博客,本文后面只会分析ConfigurationClassPostProcessor对象的创建和postProcessBeanDefinitionRegistry方法的执行
        this.refreshContext(context);
        this.afterRefresh(context, applicationArguments);
    
    • 1
    • 2

    this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);代码执行后的截图如下:
    在这里插入图片描述
    ConfigurationClassPostProcessor实现BeanFactoryPostProcessor,关于BeanFactoryPostProcessor扩展接口的作用在《spring初始化源码浅析之关键类和扩展接口》一文中有详细介绍。

    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已创建完毕,如下图:
      在这里插入图片描述
  • 相关阅读:
    滴滴9.17笔试题复盘(差分数组和前缀和数组)
    WPF--实现WebSocket服务端
    WPF --- 非Button自定义控件实现点击功能
    <Vue>使用依赖注入的方式共享数据
    两台Linux服务器之间传送文件
    【Qt QML】TabBar的用法
    葡萄酒质量预测python
    检测网络:让网络检测通过图表呈现 - gping
    危化品安全生产风险监测预警系统的构建与实施
    Monaco Editor教程(五): 实现同时多文件编辑,tab切换
  • 原文地址:https://blog.csdn.net/qq_39093474/article/details/124110591