基于@ComponentScan注解的使用详解

目录

  • @ComponentScan注解的使用
    • 一、注解定义
    • 二、使用
      • 1.环境准备
      • 2.excludeFilters的使用
      • 3.includeFilters的使用
      • 4.自定义过滤规则
  • 关于@ComponentScan注解的一些细节

    @ComponentScan注解的使用
    一、注解定义
    @ComponentScan注解的定义如下:
    @Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Repeatable(ComponentScans.class)public @interface ComponentScan {/*** 扫描路径* @ComponentScan(value = "https://www.it610.com/article/spring.annotation.componentscan")*/@AliasFor("basePackages")String[] value() default {}; /*** 扫描路径*/@AliasFor("value")String[] basePackages() default {}; /*** 指定扫描类* @ComponentScan(basePackageClasses = {BookDao.class, BookService.class})*/Class[] basePackageClasses() default {}; /*** 命名注册的Bean,可以自定义实现命名Bean,* 1、@ComponentScan(value = "https://www.it610.com/article/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 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 = "https://www.it610.com/article/spring.annotation.componentscan",scopeResolver = MyAnnotationScopeMetadataResolver.class)*/Class scopeResolver() default AnnotationScopeMetadataResolver.class; /*** 用来设置类的代理模式*/ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; /*** 扫描路径 如 resourcePattern = "**/*.class"* 使用includeFilters 和 excludeFilters 会更灵活*/String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN; /*** 指示是否应启用对带有{@code @Component},{@ code @Repository},* {@ code @Service}或{@code @Controller}注释的类的自动检测。*/boolean useDefaultFilters() default true; /*** 对被扫描的包或类进行过滤,若符合条件,不论组件上是否有注解,Bean对象都将被创建* @ComponentScan(value = "https://www.it610.com/article/spring.annotation.componentscan",includeFilters = {*@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}),*@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {SchoolDao.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*/Filter[] includeFilters() default {}; /*** 指定哪些类型不适合进行组件扫描。* 用法同 includeFilters 一样*/Filter[] excludeFilters() default {}; /*** 指定是否应注册扫描的Bean以进行延迟初始化。* @ComponentScan(value = "https://www.it610.com/article/spring.annotation.componentscan",lazyInit = true)*/boolean lazyInit() default false; /*** 用于 includeFilters 或 excludeFilters 的类型筛选器*/@Retention(RetentionPolicy.RUNTIME)@Target({})@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 = https://www.it610.com/article/{Controller.class, Service.class})* @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {SchoolDao.class})* @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})*/@AliasFor("classes")Class[] value() default {}; /*** 作用同上面的 value 相同* ANNOTATION 参数为注解类,如Controller.class, Service.class, Repository.class* ASSIGNABLE_TYPE 参数为类,如 SchoolDao.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 = "spring.annotation..*")* REGEX参数为 正则表达式* @ComponentScan.Filter(type = FilterType.REGEX, pattern = "^[A-Za-z.]+Dao$")*/String[] pattern() default {}; }}


    二、使用

    1.环境准备 创建Maven项目,添加依赖:
    org.springframeworkspring-context4.3.26.RELEASEjunitjunit4.12test

    创建bean,controller,dao,service层,并在类上加上对应的注解,项目结构如下:
    基于@ComponentScan注解的使用详解
    文章图片

    编写测试类,如下:
    public class IoCTest { @Test public void test01() {//获取Spring的IOC容器ApplicationContext applicationContext=new AnnotationConfigApplicationContext(SpringConfig.class); //从容器中获取beanString[] names= applicationContext.getBeanDefinitionNames(); for(String i:names) {System.out.println(i); } }}


    2.excludeFilters的使用 【基于@ComponentScan注解的使用详解】使用excludeFilters不扫描com.learn包中的Controller、Service注解,如下:
    @Configuration@ComponentScan(basePackages = "com.learn",excludeFilters = {@Filter(type = FilterType.ANNOTATION,classes = {Controller.class,Service.class})})public class SpringConfig {}

    上面使用的excludeFilters用于设置排除的过滤条件,实现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 参数为类,如 SchoolDao.class
    • CUSTOM 参数为实现 TypeFilter 接口的类 ,如 MyTypeFilter.class

    3.includeFilters的使用 includeFilters属性用于定义扫描过滤条件,满足该条件才进行扫描。用法与excludeFilters一样。
    但是因为useDefaultFilters属性默认为true,即使用默认的过滤器,启用对带有@Component,@Repository,@Service,@Controller注释的类的自动检测。会将带有这些注解的类注册为bean装配到IoC容器中。所以使用includeFilters时,需要把useDefaultFilters设置为false,如下:
    @Configuration@ComponentScan(basePackages = "com.learn",includeFilters = {@Filter(type = FilterType.ANNOTATION,classes = {Controller.class,Service.class})},useDefaultFilters = false)public class SpringConfig {}

    结果如下,只扫描了带有Controller,Service注解的自定义的类:
    基于@ComponentScan注解的使用详解
    文章图片


    4.自定义过滤规则 @ComponentScan注解扫描或解析的bean只能是Spring内部所定义的,比如@Component、@Service、@Controller或@Repository。如果要扫描一些自定义的注解,就可以自定义过滤规则来完成这个操作。
    自定义一个类MyTypeFilter实现TypeFilter接口,这样这个TypeFilter就扫描所有类并只通过类名包含了controller的类,如下:
    public class MyTypeFilter implements TypeFilter {/*** 两个参数的含义:* metadataReader:包含读取到的当前正在扫描的类的信息* metadataReaderFactory:可以获取到当前正在扫描的类的其他类信息(如父类和接口)* match方法返回false即不通过过滤规则,true通过过滤规则*/ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)throws IOException {// TODO Auto-generated method stub//获取当前类注解的信息AnnotationMetadata annotationMetadata = https://www.it610.com/article/metadataReader.getAnnotationMetadata(); //获取当前正在扫描的类的类信息ClassMetadata classMetadata = metadataReader.getClassMetadata(); //获取当前类资源(类的路径)Resource resource = metadataReader.getResource(); String className = classMetadata.getClassName(); if(className.contains("controller")){return true; }return false; }}

    在@ComponentScan注解中进行配置,如下:
    @Configuration@ComponentScan(basePackages = "com.learn",includeFilters = {@Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {Person.class}),@Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class}),},useDefaultFilters = false)public class SpringConfig {}

    经过上面的配置,第一个@Filter通过FilterType.ASSIGNABLE_TYPE规定了只扫描Person类型的类,第二个@Filter通过FilterType.CUSTOM自定义过滤规则规定了只扫描类名包含controller的类。结果:
    基于@ComponentScan注解的使用详解
    文章图片


    关于@ComponentScan注解的一些细节 @ComponentScan注解可以扫描 任意 类 或者 注解 中内部类bean;
    要想被扫描的前提是内部类标注了@Component及其相关衍生注解、内部类必须是static修饰,否则扫描不到;如果是注解的内部类则只能是public static修饰
    //@Configurationpublic class ProfileConfig {@Component("class1")private static class Class1 {@Beanpublic Class3 class3() {return new Class3(); }}@Component("class2")static class Class2 {}//@Component("class3")protected static class Class3 {}//@Component("class4") //出错//protected class Class4 {////}}--------------------------------------------------------@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)//@Beanpublic @interface MyBean {public static final int age1=3; //常量可以int age=2; public abstract String value() default "qwer"; //属性可以//内部类可以@Component@DependsOn("myBean.EventZ") //依赖事件bean的创建,保证该bean在事件bean之后创建//细节:因为是内部类,所以默认bean的id是类名首字母小写,不是eventZ而是myBean.EventZclass EventSource {public EventSource(){System.out.println("事件源创建了..."); }}@Component//("eventZ")public static class EventZ {public EventZ(){System.out.println("事件创建了..."); }}}

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

      推荐阅读