@ComponentScan注解 -【Spring底层原理】

案例已上传GitHub,欢迎star:github.com/oneStarLR/s…java

1、注解用法

1. 背景知识

什么是组件?git

组件也是抽象的概念,能够理解为一些符合某种规范的类组合在一块儿就构成了组件,他能够提供某些特定的功能,但实际他们都是类,只不过有他们特殊的规定。组件和类的关系:符合某种规范的类的组合构成组件。github

2. @ComponentScan注解做用

1. 将组件自动加载到容器

加了包扫描@ComponentScan注解后,只要标注了@Controller、@Service、@Repository、@Component注解中的任何一个,其组件都会被自动扫描,加入到容器中。正则表达式

2. 经过属性指定扫描

【1】value:指定要扫描的包spring

【2】excludeFilters=Filter[ ]:设置排除的过滤条件,指定扫描的时候按照什么规则排除哪些组件,不扫描哪些包数组

【3】includeFilters=filter[ ]:设置扫描过滤条件,指定扫描的时候按照什么规则包含哪些组件,知足该条件才进行扫描markdown

【4】自定义过滤规则:经过实现TypeFilter接口,自定义过滤规则app

下面我们经过实例来分析一下@ComponentScan注解的做用dom

2、实例分析

以maven项目为例,经过@ComponentScan、@Controller、@Service、@Repository注解获取容器中的对象:maven

// 启动类
@Test
public void TestMain(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    String[] beanNames = applicationContext.getBeanDefinitionNames();
    for(String beanName : beanNames){
        System.out.println("容器中对象名称:"+beanName);
    }
}

// 配置类
@Configuration
@ComponentScan(value = "com.onestar")
public class AppConfig {
}

// 控制层
@Controller
public class UserController {
}

// 业务层
@Service
public class UserService {
}

// 持久层
@Repository
public class UserDao {
}
复制代码

运行测试类,能够看到以下打印信息,除了spring启动时注入到容器的对象外,还有咱们本身加了注解,被spring扫描,加入到容器中

  • @ComponentScan(value = "com.onestar"):value表示指定要扫描的包

image-20210120110132056

点进@ComponentScan注解源码,咱们能够看到,使用value能够指定要扫描的包,咱们还能够排除要扫描的包,包含要扫描的包,甚至还能够自定义过滤规则

  • excludeFilters=Filter[ ]:指定扫描的时候按照什么规则排除哪些包
  • includeFilters=filter[ ]:指定扫描的时候是须要包含哪些包

【@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, * 一、@ComponentScan(value = "spring.annotation.componentscan",nameGenerator = MyBeanNameGenerator.class) * MyBeanNameGenerator.class 须要实现 BeanNameGenerator 接口,全部实现BeanNameGenerator 接口的实现类都会被调用 * 二、使用 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

经过上面的excludeFilters注解配置,我们再来运行一下测试类,打印信息以下,能够看到,标有@Controller和@Service注解的组件不被扫描:

image-20210120115812025

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:禁用默认扫描规则

经过上面的includeFilters注解配置,我们再来运行一下测试类,打印信息以下,能够看到,标有@Controller和@Service注解的组件才被扫描:

image-20210120133305643

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:指定自定义过滤参数

运行测试类,能够看到打印信息,包含“serv”的组件进行扫描:

image-20210120150558862

3、源码追踪

一样,我们以AnnotationConfigApplicationContext类为入口,点进去跟踪源码:

image-20210120153916014

经过refresh()跟踪Bean 工厂的后置处理器调用:

image-20210120154049710

继续跟踪,查看源码,这里实例化并调用全部已注册的BeanFactoryPostProcessor

image-20210120154529999

继续跟踪,主方法中有一些加载顺序之类的,找到invokeBeanDefinitionRegistryPostProcessors,查看调用Bean的注册定义的后置处理器的方法,进入源码查看:

image-20210120154800082

继续跟踪,这里有个循环,会把全部的处理器拿出来进行处理:

image-20210120154908717

继续跟踪,能够看到是一个接口,进入查看实现类,咱们能够看到一个配置Bean的定义,就是指咱们Configuration配置的里面的一些Bean的定义:

image-20210120155028837

继续跟踪,processConfigBeanDefinitions类中能够看到Configuration类的解析器,经过parser.parse(candidates);把要扫描的东西进行解析

image-20210120155811376

继续跟踪,判断咱们声明的是不是一个注解Bean,是的话,再进行解析

image-20210120155951900

继续跟踪:

image-20210120160043671

继续跟踪,在processConfigurationClass方法中找到和配置相关的方法:

image-20210120160159810

继续跟踪,能够看到和@ComponentScan注解注解相关的东西了,咱们进入componentScanParser.parse看其具体的解析方法:

image-20210120160559967

继续跟踪,查看构造方法:

image-20210120160754435

继续跟踪,前面咱们讲到useDefaultFilters表示默认扫描规则,默认是true:

image-20210120160857561

跟踪到这就能够了,就是在这里将全部标有@Component注解的组件都添加到咱们的includeFiltes里面去,而@Controller、@Service、@Repository注解都标有@Component注解,因此只要标注了@Controller、@Service、@Repository、@Component注解中的任何一个,其组件都会被自动扫描,加入到容器中

image-20210120161124699

相关文章
相关标签/搜索