Spring注解02——@ComponentScan 灵活控制扫描范围

该系列文章主要学习 雷丰阳老师的《Spring注解驱动》课程总结。
原课程地址:课程地址

包括了自己阅读其他书籍《Spring揭秘》《Spring Boot 实战》等课程。该系列文档会不断的完善,欢迎大家留言及提意见。

文章目录

    • 1. 增加包扫描注解
    • 2. 高级过滤规则
      • 1. excludeFilters 使用
      • 1. includeFilters 使用
    • 3. 各种类型过滤
    • 总结

以前可以通过xml来控制包扫描的范围,如今如果只是利用注解,如何合理的控制包扫描的范围呢?

注意:凡是在指定的包或其子包中的类上标注了@Repository、@Service、@Controller、@Component注解的类都会被扫描到,并将这个类注入到Spring容器中。其他的比如 @Configuration、@Bean是不受影响的。

定义两个演示的类,分别标注有 @Repository、@Service,其余的注解跟该方法类似

@Repository
public class BookDao {
}
@Service
public class BookService {
}

还是继续利用之前定义的配置类

@Configuration(value = "mainConfig")
public class MainConfig {

	// @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
	@Primary
	@Bean(value = "child")
	public Person personChild() {
		return new Person("张三", 20);
	}

	@Bean(value = "adult")
	public Person personAdult() {
		return new Person("蜡笔小新", 20);
	}
	
}

在不加包扫描的前提下查看容器中所有的组件信息

public class TestMain {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        printAllBean(applicationContext);
    }

    private static void printAllBean(ApplicationContext applicationContext) {
        System.out.println("=====所有的Bean对象=====");
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String s : beanDefinitionNames) {
            System.out.println(s);
        }
    }
}

查看现有组件列表

=====所有的Bean对象=====
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookDao
bookService
child
adult

1. 增加包扫描注解

在 MainConfig 类上增加包扫描注解信息,只想扫描 com.hone.learn.dao 路径下的组件

@Configuration(value = "mainConfig")
@ComponentScan(value = "com.hone.learn.dao")
public class MainConfig {

	// @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
	@Primary
	@Bean(value = "child")
	public Person personChild() {
		return new Person("张三", 20);
	}

	@Bean(value = "adult")
	public Person personAdult() {
		return new Person("蜡笔小新", 20);
	}
	
}

查看输出结果发现 bookService 已经不输出了

=====所有的Bean对象=====
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookDao
child
adult

2. 高级过滤规则

可以指定只包含或者排除某些条件

Filter[] includeFilters() default {};


Filter[] excludeFilters() default {};

两个方法的返回值都是Filter[]数组,在ComponentScan注解类的内部存在Filter注解类,大家可以看下上面的代码。

1. excludeFilters 使用

现在有这样一个需求,除了@Controller和@Service标注的组件之外,IOC容器中剩下的组件我都要,即相当于是我要排除@Controller和@Service这俩注解标注的组件。要想达到这样一个目的,我们可以在MainConfig类上通过@ComponentScan注解的excludeFilters()方法实现。例如,我们在MainConfig类上添加了如下的注解。

@Configuration(value = "mainConfig")
@ComponentScan(value = "com.hone.learn", excludeFilters =
        {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Repository.class})})
public class MainConfig {

最后 扫描容器中所有的组件,发现已经不包括 bookDao 了,已经根据规则合理的排除了。

=====所有的Bean对象=====
//省略自带的bean输出结果
mainConfig
bookService
child
adult

1. includeFilters 使用

当我们使用includeFilters()方法来指定只包含哪些注解标注的类时,需要禁用掉默认的过滤规则,如果只想扫描备注有 @Repository 的组件。

/**
 * 只想获取 Repository 组件
 */
@ComponentScan(value = "com.hone.learn", includeFilters =
		{@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Repository.class}) },
		useDefaultFilters = false)
@Configuration(value = "mainConfig")
public class MainConfig {

在输出的结果中,只包含了@Repository注解标注的组件名称,并没有输出@Service注解标注的组件名称。

=====所有的Bean对象=====
// 省略Spring自带的组件
mainConfig
bookDao
child
adult

3. 各种类型过滤

上面只是使用了 FilterType.ANNOTATION ,根据名称知道这个肯定是根据注解名称来判断的。但是Spring本身提供了多种方式

分别有

  1. ANNOTATION:按照是否注解名称来判断
  2. ASSIGNABLE_TYPE:按照给定的类型是否是指定类型的类或者子类进行包含或者排除
  3. ASPECTJ:按照ASPECTJ表达式进行包含或者排除
  4. REGEX:按照正则表达式进行包含或者排除
  5. CUSTOM:按照自定义规则进行包含或者排除

前四种的使用方式基本上跟 第一种按照注解的方式类似,使用上没有什么区别。值得说明的是第五种自定义的类型。

该匹配规则只是定义了一个抽象的方法,如果用户需要自定义,可以实现该方法。

比如这里我定义一个属于自己的过滤规则器

当我们实现TypeFilter接口时,需要实现该接口中的match()方法,match()方法的返回值为boolean类型。当返回true时,表示符合规则,会包含在Spring容器中;当返回false时,表示不符合规则,那就是一个都不匹配,自然就都不会被包含在Spring容器中。另外,在match()方法中存在两个参数,分别为MetadataReader类型的参数和MetadataReaderFactory类型的参数,含义分别如下。

  1. metadataReader:读取到的当前正在扫描的类的信息
  2. metadataReaderFactory:可以获取到其他任何类的信息的工厂

比如我下面实现一个自定义的 filter,规则是:只有类名包含 “Dao” 才注入到容器中

public class SelfTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        String className = classMetadata.getClassName();
        if (className.contains("Dao")) {
            return true;
        }
        return false;
    }
}

然后定义配置类

@ComponentScan(value = "com.hone.learn", includeFilters =
        {@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {SelfTypeFilter.class}) },
        useDefaultFilters = false)
@Configuration(value = "mainConfig")
public class MainConfig {

    // @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
    @Primary
    @Bean(value = "child")
    public Person personChild() {
        return new Person("张三", 20);
    }

    @Bean(value = "adult")
    public Person personAdult() {
        return new Person("蜡笔小新", 20);
    }
}

最后还是利用测试类,输出所有的 组件

public class TestMain {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        printAllBean(applicationContext);
    }


    private static void printAllBean(ApplicationContext applicationContext) {
        System.out.println("=====所有的Bean对象=====");
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String s : beanDefinitionNames) {
            System.out.println(s);
        }
    }
}

通过输出结果可以发现,BookService 已经不包括在内部了

=====所有的Bean对象=====
// 忽略自带组件
mainConfig
bookDao
child
adult

总结

我们可以使用 @ComponentScan 注解来指定 Spring 扫描哪些包,可以使用 excludeFilters() 方法来指定扫描时排除哪些组件;也可以使用 includeFilters() 方法来指定扫描时只包含哪些组件。

当使用includeFilters()方法指定只包含哪些组件时,需要禁用掉默认的过滤规则。

你可能感兴趣的:(Spring注解驱动)