【SpringBoot】【三】 @ComponentScan 执行时机
1 前言
我们都知道 SpringBoot 启动的时候,@SpringBootApplication 注解里是会有一个@ComponentScan注解,用于扫描当前启动类目录下的所有组件,那它是什么时候执行的呢,具体的执行过程是怎么样的我们这节就来看一下。
2 @ComponentScan 注解作用
(1)将组件自动加载到容器,加了包扫描@ComponentScan注解后,只要标注了@Controller、@Service、@Repository、@Component注解中的任何一个,其组件都会被自动扫描,加入到容器中。
(2)通过属性指定扫描
- value:指定要扫描的包
- excludeFilters=Filter[ ]:设置排除的过滤条件,指定扫描的时候按照什么规则排除哪些组件,不扫描哪些包
- includeFilters=filter[ ]:设置扫描过滤条件,指定扫描的时候按照什么规则包含哪些组件,满足该条件才进行扫描
- 自定义过滤规则:通过实现TypeFilter接口,自定义过滤规则
3 源码通读
3.1 @ComponentScan 注解
那我们先来看下 @ComponentScan注解有哪些属性信息:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Repeatable(ComponentScans.class) public @interface ComponentScan { // 扫描路径 @AliasFor("basePackages") String[] value() default {}; // 扫描路径 @AliasFor("value") String[] basePackages() default {}; // 指定扫描类 Class<?>[] basePackageClasses() default {}; /** * 命名注册的Bean,可以自定义实现命名Bean, * 1、@ComponentScan(value = "spring.annotation.componentscan",nameGenerator = MyBeanNameGenerator.class) * MyBeanNameGenerator.class 需要实现 BeanNameGenerator 接口,所有实现BeanNameGenerator 接口的实现类都会被调用 * 2、使用 AnnotationConfigApplicationContext 的 setBeanNameGenerator方法注入一个BeanNameGenerator * BeanNameGenerator beanNameGenerator = (definition,registry)-> String.valueOf(new Random().nextInt(1000)); * AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); * annotationConfigApplicationContext.setBeanNameGenerator(beanNameGenerator); * annotationConfigApplicationContext.register(MainConfig2.class); * annotationConfigApplicationContext.refresh(); * 第一种方式只会重命名@ComponentScan扫描到的注解类 * 第二种只有是初始化的注解类就会被重命名 * 列如第一种方式不会重命名 @Configuration 注解的bean名称,而第二种就会重命名 @Configuration 注解的Bean名称 */ Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; /** * 用于解析@Scope注解,可通过 AnnotationConfigApplicationContext 的 setScopeMetadataResolver 方法重新设定处理类 * ScopeMetadataResolver scopeMetadataResolver = definition -> new ScopeMetadata(); 这里只是new了一个对象作为演示,没有做实际的逻辑操作 * AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); * annotationConfigApplicationContext.setScopeMetadataResolver(scopeMetadataResolver); * annotationConfigApplicationContext.register(MainConfig2.class); * annotationConfigApplicationContext.refresh(); * 也可以通过@ComponentScan 的 scopeResolver 属性设置 *@ComponentScan(value = "spring.annotation.componentscan",scopeResolver = MyAnnotationScopeMetadataResolver.class) */ Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class; // 设置类的代理模式 ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; /** * 扫描路径 如 resourcePattern = "**/*.class" * 使用 includeFilters 和 excludeFilters 会更灵活 */ String resourcePattern() default "**/*.class"; /** * 指示是否应启用对带有{@code @Component},{@ code @Repository}, * {@ code @Service}或{@code @Controller}注释的类的自动检测。 */ boolean useDefaultFilters() default true; /** * 对被扫描的包或类进行过滤,若符合条件,不论组件上是否有注解,Bean对象都将被创建 * @ComponentScan(value = "com.onestar",includeFilters = { * @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}), * @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {UserDao.class}), * @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}), * @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "spring.annotation..*"), * @ComponentScan.Filter(type = FilterType.REGEX, pattern = "^[A-Za-z.]+Dao$") * },useDefaultFilters = false) * useDefaultFilters 必须设为 false */ ComponentScan.Filter[] includeFilters() default {}; // 设置排除的过滤条件,指定扫描的时候按照什么规则排除哪些组件,排除要扫描的包,用法参考includeFilters ComponentScan.Filter[] excludeFilters() default {}; /** * 指定是否应注册扫描的Bean以进行延迟初始化。 * @ComponentScan(value = "com.onestar",lazyInit = true) */ boolean lazyInit() default false; // @Filter注解,用于 includeFilters 或 excludeFilters 的类型筛选器 @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Filter { /** * 要使用的过滤器类型,默认为 ANNOTATION 注解类型 * @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}) */ FilterType type() default FilterType.ANNOTATION; /** * 过滤器的参数,参数必须为class数组,单个参数可以不加大括号 * 只能用于 ANNOTATION 、ASSIGNABLE_TYPE 、CUSTOM 这三个类型 * @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class, Service.class}) * @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {UserDao.class}) * @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) */ @AliasFor("classes") Class<?>[] value() default {}; /** * 作用同上面的 value 相同 * ANNOTATION 参数为注解类,如 Controller.class, Service.class, Repository.class * ASSIGNABLE_TYPE 参数为类,如 UserDao.class * CUSTOM 参数为实现 TypeFilter 接口的类 ,如 MyTypeFilter.class * MyTypeFilter 同时还能实现 EnvironmentAware,BeanFactoryAware,BeanClassLoaderAware,ResourceLoaderAware * 这四个接口 * EnvironmentAware * 此方法用来接收 Environment 数据 ,主要为程序的运行环境,Environment 接口继承自 PropertyResolver 接口, * 详细内容在下方 * @Override * public void setEnvironment(Environment environment) { * String property = environment.getProperty("os.name"); * } * * BeanFactoryAware * BeanFactory Bean容器的根接口,用于操作容器,如获取bean的别名、类型、实例、是否单例的数据 * @Override * public void setBeanFactory(BeanFactory beanFactory) throws BeansException { * Object bean = beanFactory.getBean("BeanName") * } * * BeanClassLoaderAware * ClassLoader 是类加载器,在此方法里只能获取资源和设置加载器状态 * @Override * public void setBeanClassLoader(ClassLoader classLoader) { * ClassLoader parent = classLoader.getParent(); * } * * ResourceLoaderAware * ResourceLoader 用于获取类加载器和根据路径获取资源 * public void setResourceLoader(ResourceLoader resourceLoader) { * ClassLoader classLoader = resourceLoader.getClassLoader(); * } */ @AliasFor("value") Class<?>[] classes() default {}; /** * 这个参数是 classes 或 value 的替代参数,主要用于 ASPECTJ 类型和 REGEX 类型 * ASPECTJ 为 ASPECTJ 表达式 * @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "com.onestar..*") * REGEX 参数为 正则表达式 * @ComponentScan.Filter(type = FilterType.REGEX, pattern = "^[A-Za-z.]+Dao$") */ String[] pattern() default {}; } }
(1)excludeFilters=Filter[ ]
通过excludeFilters=Filter[ ]来排除要扫描的包,在配置类注解中修改:
@Configuration @ComponentScan(value = "com.onestar",excludeFilters = { @Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})}) public class AppConfig { }
- type:表示过滤类型,这里是按照注解进行过滤
- classes:表示过滤器参数,这里是过滤掉有@Controller和@Service注解的组件
通过实现Filter接口的type属性用于设置过滤类型,默认值为FilterType.ANNOTATION,提供了这几个过滤类型:
- FilterType.ANNOTATION:按照注解过滤
- FilterType.ASSIGNABLE_TYPE:按照给定的类型过滤
- FilterType.ASPECTJ:按照ASPECTJ表达式过滤
- FilterType.REGEX:按照正则表达式过滤
- FilterType.CUSTOM:按照自定义规则过滤
classes和value属性为过滤器的参数,必须为class数组,类只能为以下三种类型:
- ANNOTATION 参数为注解类,如 Controller.class, Service.class, Repository.class
- ASSIGNABLE_TYPE 参数为类,如 UserDao.class
- CUSTOM 参数为实现 TypeFilter 接口的类 ,如 MyTypeFilter.class
(2)includeFilters=filter[ ]
通过includeFilters=Filter[ ]只需要包含要扫描的包,在配置类注解中修改,默认的规则是扫描value下所有的包,所以我们要禁用这个规则:
@Configuration @ComponentScan(value = "com.onestar",includeFilters = { @Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})},useDefaultFilters = false) public class AppConfig { }
useDefaultFilters = false:禁用默认扫描规则,默认的扫描规则就是扫@Component注解的类,下边我们会说。
(3) `FilterType.CUSTOM`
上面提到,可以使用FilterType.CUSTOM按照自定义规则进行过滤,通过源码,我们知道,可以实现TypeFilter的实现类来进行自定义过滤:
public class MyTypeFilter implements TypeFilter { /** * @description TODO * @author ONESTAR * @date 2021/1/20 14:29 * @param metadataReader:读取到当前正在扫描的类的信息 * @param metadataReaderFactory:可以获取到其他任何类的信息 * @throws * @return boolean */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { // 获取当前类注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); // 获取当前正在扫描的类的信息 ClassMetadata classMetadata = metadataReader.getClassMetadata(); // 获取当前类资源 Resource resource = metadataReader.getResource(); String className = classMetadata.getClassName(); // 指定包含“serv”的组件进行扫描 if(className.contains("serv")){ return true; } return false; } }
该实现类通过获取当前所有资源组件,并进行指定扫描,这里包含“serv”的组件进行扫描,然后修改配置类指定自定义扫描规则:
@Configuration @ComponentScan(value = "com.onestar",includeFilters = { @Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})},useDefaultFilters = false) public class AppConfig { }
MyTypeFilter.class:指定自定义过滤参数
3.2 执行时机
接下来我们看看执行的时机,首先入口是在上下文刷新的时候执行 BeanFactory的后置处理的时候:
接着进入,这里实例化并调用所有已注册的BeanFactoryPostProcessor:
继续跟踪,主方法中有一些加载顺序之类的,找到invokeBeanDefinitionRegistryPostProcessors,查看调用Bean的注册定义的后置处理器的方法,进入源码查看:
继续跟踪,这里有个循环,会把所有的处理器拿出来进行处理:
继续跟踪,可以看到是一个接口,进入查看实现类,我们可以看到一个配置Bean的定义,就是指我们Configuration配置的里面的一些Bean的定义(提一下这里也会扫@Configuration):
继续跟踪,processConfigBeanDefinitions类中可以看到Configuration类的解析器,通过parser.parse(candidates);把要扫描的东西进行解析:
继续跟踪,判断我们声明的是否是一个注解Bean,是的话,再进行解析:
继续跟踪:
继续跟踪,在processConfigurationClass方法中找到和配置相关的方法:
继续跟踪,可以看到和@ComponentScan注解注解相关的东西了,我们进入componentScanParser.parse看其具体的解析方法:
继续跟踪,查看构造方法:
继续跟踪,前面我们讲到useDefaultFilters表示默认扫描规则,默认是true:
跟踪到这就可以了,就是在这里将所有标有@Component注解的组件都添加到我们的includeFiltes里面去,而@Controller、@Service、@Repository注解都标有@Component注解,所以只要标注了@Controller、@Service、@Repository、@Component注解中的任何一个,其组件都会被自动扫描,加入到容器中:
调试跟踪:
那我们整体画个图来理解一下哈:
4 小结
好啦,我们本节了解了下@ComponentScan的内容以及执行的时机,具体的收集过程没看哈,太多了哈哈= =后续看了再补充哈,有理解不对的地方欢迎指正哈。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了