Spring Boot注解学习之@SpringBootApplication(二)

2,723 阅读10分钟

@ComponentScan属性basePackages 与 valuebasePackageClassesincludeFiltersexcludeFilters添加自定义过滤规则@Component@ComponentScanscontext:component-scanSpringBootApplication 注解中4个方法小结参考文献

@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 容器注册 internalConfigurationAnnotationProcessorinternalAutowiredAnnotationProcessorinternalCommonAnnotationProcessorinternalEventListenerProcessor 以及 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 定义。

参考文献

spring4.0之二:@Configuration的使用

springboot系列文章之SpringBootApplication注解