@ComponentScan
在讲述 @Configuration
启动容器+@Component
注册 Bean 小节中简单介绍了@ComponentScan
注解的使用。
@ComponentScan
的功能其实就是自动扫描并加载符合条件的组件或 bean 定义,最终将这些 bean 定义加载到容器中。我们可以通过 backPackages 等属性指定@ComponentScan 自动扫描的范围,如果不指定,则默认 Spring 框架实现从声明@ComponentScan 所在类的 package 进行扫描,默认情况下是不指定的,所以 SpringBoot 的启动类最好在 root package 下。
首先看下@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 {}; Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class; ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; String resourcePattern() default "**/*.class"; boolean useDefaultFilters() default true; ComponentScan.Filter[] includeFilters() default {}; ComponentScan.Filter[] excludeFilters() default {}; boolean lazyInit() default false; @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Filter { FilterType type() default FilterType.ANNOTATION; @AliasFor("classes") Class<?>[] value() default {}; @AliasFor("value") Class<?>[] classes() default {}; String[] pattern() default {}; } } 复制代码
重点有以下几个属性:
- basePackages 与 value:用于指定包的路径进行扫描;
- basePackageClasses:用于指定某个类的包的路径进行扫描;
- nameGenerator:bean 的名称的生成器;
- useDefaultFilters:是否开启对@Component,@Repository,@Service,@Controller 的类进行检测,默认为 true;
- includeFilters:包含的过滤条件 FilterType.ANNOTATION:按照注解过滤; FilterType.ASSIGNABLE_TYPE:按照给定的类型;FilterType.ASPECTJ:使用 ASPECTJ 表达式;FilterType.REGEX:正则;FilterType.CUSTOM:自定义规则 。
- excludeFilters:排除的过滤条件,用法和 includeFilters 一样。
属性
basePackages 与 value
@ComponentScan(basePackages = “”) //单个 @ComponentScan(basePackages = {“com.example.dao”,“aaa”,“…”}) //多个 //value 同理 复制代码
注意:可以省略 “ basePackages = ”。
@Configuration @ComponentScan("com.example.dao") public class MyConfig {} @Configuration @ComponentScan("com.example.dao","com.example.service") public class MyConfig {} @Configuration @ComponentScan("com.example.*") //通配符匹配所有的包 public class MyConfig {} 复制代码
basePackageClasses
@ComponentScan(basePackageClasses = “”) //单个 @ComponentScan(basePackageClasses = {“HelloController.class”,“bbb”,“…”}) //多个 复制代码
注意:不可以省略“basePackageClasses =”
@Configuration @ComponentScan(basePackageClasses = HelloController.class) public class MyConfig { } 复制代码
includeFilters
接下来我们用以下代码进行测试。
Repository 的注解类:
package com.example.dao; @Repository public class BusinessDAO { public void update(){ System.out.println("调用了 dao 层的 update 方法...."); } } 复制代码
Service 的注解类:
package com.example.service; @Service public class BusinessService { @Autowired private BusinessDAO businessDAO; public void service(){ System.out.println("调用了 service 层的 service() 方法 ....."); businessDAO.update(); } } 复制代码
Controller 的注解类:
package com.example.controller; @Controller public class BusinessController { @Autowired private BusinessService service; public void request() { System.out.println(" 调用了 Controller 的 request() 方法..."); service.service(); } } 复制代码
Configuration 配置类:
package com.example.configuration; @Configuration //@ComponentScan(basePackages = {"com.example.dao","com.example.service","com.example.controller"}) @ComponentScan(value = {"com.example.dao","com.example.service","com.example.controller"}, includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)}, useDefaultFilters = false) public class ScanConfig { } 复制代码
测试类代码:
public class ScanConfigTest { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanConfig.class); String[] definitionNames = context.getBeanDefinitionNames(); for(String name:definitionNames){ System.out.println(name); } } } 复制代码
执行结果为:
19:22:48.157 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'scanConfig' 19:22:48.162 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessDAO' org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory scanConfig businessDAO 复制代码
根据结果分析: 除了 spring 本身注册的一些 bean 之外,可以看到最后一行,已经将 ScanConfig 这个类和 BusinessDAO 注册进容器中了。
includeFilters 的参数是一个 Filter[] 数组,然后指定 FilterType 的类型为 ANNOTATION,也就是通过注解来过滤,最后的 value 则是 Repository 注解类。配置之后,在 spring 扫描的时候,就会筛选 com.example 下的三个包中,所有被 @Repository 注解标注的类。
注意:需要设置 useDefaultFilters 为 false,否则@ComponentScan 注解会将 被 @Component、@Repository、@Service 和 @Controller 标注的类都注册进容器中。同时因为我们在 Controller 和 Service 注解的类中关联了其他的类(@Autowired),所以最好在 includeFilters 的 Filter 属性中不要设置为 Controller.class 或 Service.class。
excludeFilters
修改配置类:
@Configuration //@ComponentScan(basePackages = {"com.example.dao","com.example.service","com.example.controller"}) @ComponentScan(value = {"com.example.dao","com.example.service","com.example.controller"}, excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)}) public class ScanConfig { } 复制代码
同 excludeFilters 属性类似, 配置之后,在 spring 扫描的时候,就会跳过 com.example 下的三个包中,所有被 @Controller 注解标注的类。
执行结果为:
19:44:39.191 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'scanConfig' 19:44:39.195 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessDAO' 19:44:39.195 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessService' org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory scanConfig businessDAO businessService 复制代码
添加自定义过滤规则
在前面使用过 @Filter 注解,里面的 type 属性是一个 FilterType 的枚举类型:
public enum FilterType { ANNOTATION, ASSIGNABLE_TYPE, ASPECTJ, REGEX, CUSTOM } 复制代码
使用 CUSTOM 类型,就可以实现自定义过滤规则。
package com.example.filter; public class CustomTypeFilter implements TypeFilter { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { // 获取当前扫描到的类的注解元数据 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); // 获取当前扫描到的类的元数据 ClassMetadata classMetadata = metadataReader.getClassMetadata(); // 获取当前扫描到的类的资源信息 Resource resource = metadataReader.getResource(); if (classMetadata.getClassName().contains("Business")){ return true; } return false; } } 复制代码
增加 Service 注解修饰的类:
package com.example.service; @Service public class UserService { } 复制代码
修改配置类:
@Configuration @ComponentScan(value = {"com.example.dao","com.example.service","com.example.controller"}, excludeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,classes = CustomTypeFilter.class)}) public class ScanConfig { } 复制代码
执行测试类代码,结果为:
19:56:25.419 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'scanConfig' 19:56:25.424 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'userService' org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory scanConfig userService 复制代码
这里简单对扫描到的类名进行判断,如果类名包含”Business“的就符合条件,就会被剔除,不会注入到容器中。
@Component
1、不指定 bean 的名称,默认为类名首字母小写 university
@Component public class BeanWithComponent { public void sayHello(){ System.out.println("BeanWithComponent sayHello..."); } public void start(){ System.out.println("BeanWithComponent 初始化。。。"); } public void cleanUp(){ System.out.println("BeanWithComponent 销毁。。。"); } } 复制代码
获取 bean 方式:
@Autowired BeanWithComponent beanWithComponent; 复制代码
或
ApplicationContext context = new AnnotationConfigApplicationContext(ComfigureWithScan.class); BeanWithComponent bean = (BeanWithComponent) context.getBean("beanWithComponent"); 复制代码
2、指定 bean 的名称
@Component("bean2") public class BeanWithComponent { } 复制代码
获取 bean 方式:
@Autowired BeanWithComponent bean2; 复制代码
或
ApplicationContext context = new AnnotationConfigApplicationContext(ComfigureWithScan.class); BeanWithComponent bean = (BeanWithComponent) context.getBean("bean2"); 复制代码
总结
@ComponentScan
注解有以下特性:
- 自定义扫描路径下边带有
@Controller
,@Service
,@Repository
,@Component
注解加入 spring 容器 - 通过 includeFilters 加入扫描路径下没有以上注解的类加入 spring 容器
- 通过 excludeFilters 过滤出不用加入 spring 容器的类
- 自定义增加了@Component 注解的注解方式
接下来讲述一下关于@ComponentScan 注解的两个扩展。
@ComponentScans
源码如下:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented public @interface ComponentScans { ComponentScan[] value(); } 复制代码
配置类:
@Configuration @ComponentScans(value = @ComponentScan("com.example.dao")) public class ScanConfig { } 复制代码
执行结果为:
20:07:45.908 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'scanConfig' 20:07:45.914 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessDAO' org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory scanConfig businessDAO 复制代码
context:component-scan
上述代码我们是通过@Configuration
+ @ComponentScan
注解来实现 spring 加载,当然也可以在配置文件中加上扫描的配置。
spring-context.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!--<context:component-scan base-package="com.example.dao" annotation-config="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan>--> <context:component-scan base-package="com.example.dao" /> <context:component-scan base-package="com.example.controller" /> <context:component-scan base-package="com.example.service" /> </beans> 复制代码
修改测试类代码:
public class ScanConfigTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); String[] definitionNames = context.getBeanDefinitionNames(); for(String name:definitionNames){ System.out.println(name); } } } 复制代码
运行得到以下结果:
14:59:12.773 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessDAO' 14:59:12.778 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessController' 14:59:12.789 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessService' 14:59:12.789 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'userService' businessDAO org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory businessController businessService userService 复制代码
关于 的使用,关于属性的详细讲解可以参考:详解
注意: 中有一个 annotation-config 属性,该属性主要是隐式地向 Spring 容器注册 internalConfigurationAnnotationProcessor
、internalAutowiredAnnotationProcessor
、internalCommonAnnotationProcessor
、internalEventListenerProcessor
以及 internalEventListenerFactory
这五个 bean 类。
当 annotation-config 属性值设为 false 时,即不会注册这五个类。但是@ComponentScan
注解没有该属性。 这是因为在几乎所有情况下,使用@ComponentScan
时,都假定使用默认的注释配置处理(例如,处理@Autowired 和 friends )。 此外,在使用AnnotationConfigApplicationContext
时,注释配置处理器始终会被注册,这意味着在@ComponentScan
级别禁用它们的任何尝试都将被忽略。
SpringBootApplication 注解中4个方法
@SpringBootApplication不仅包括上面的三个重要注解,还包含有4个方法:
Class[] exclude() default {};
根据 Class 来排除特定的类加入 Spring 容器,传入参数是 class 类型;String[] excludeName() default {};
根据 Class Name 排除特定的类加入 Spring 容器,传入参数是 class 的全类名字字符串数组;String[] scanBasePackages() default {};
指定扫描包,参数是包名的字符串数组;Class[] scanBasePackageClasses() default {};
指定扫描包,参数是 Class 类型数组。
小结
这里总结下@SpringBootApplication
中的三个重要注解的特征:
@Configuration
定义 Spring Ioc 容器的配置类。
@EnableAutoConfiguration
从 classpath 中搜寻所有 META/spring.factories 配置文件,并将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration
对应的配置项,也就是一个自动配置类列表加载到 Ioc 容器中。 简单说,就是@EnawebleAutoConfiguration
让 Spring Boot 根据类路径下的 jar 包依赖为当前项目进行自动配置,例如,添加了 spring-boot-starter-web 依赖,会自动添加 Tomcat 和 Spring MVC 的依赖。而对于所有标注@Configuration
的配置类,统一使用ConfigurationClassParser
解析的。
@ComponentScan
自动扫描并加载符合条件的组件或者 bean 定义。