@SpringBootApplication

我们知道,一个springboot项目和一个普通的web项目相比最大的优势就在于其改良了原本笨重的配置,内部自身实现了很多第三方库,使得项目精简了不少。当然启动一个springboot项目只需从主程序类的main函数进入,@SpringBootApplication注解会自动为我们扫描配置,最终成功启动项目。那么@SpringBootApplication注解究竟有什么神奇的作用呢?

了解@SpringBootApplication

@SpringBootApplication注解是一个组合注解,通过翻看@SpringBootApplication注解的源码我们发现,它是由下列的注解组合而成:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

眼尖的同学一眼就能看到有我们在Spring中学过的注解@ComponentScan,以及和配置相关的注解

@EnableAutoConfiguration和@SpringBootConfiguration,下面我们就来具体了解一下这些注解有什么作用。(按代码中注解的顺序,无重要性之分)

@Target

@Target是元注解,它的作用是用来修饰注解的作用范围,注解Target中有一个枚举类ElementType的数组变量value,说明注解的作用范围可以定义为一个数组,该枚举类如下所示:

public enum ElementType {
    TYPE,  //类、接口(包括注解类型) 或enum
    FIELD,  //域
    METHOD,  //方法
    PARAMETER,  //参数
    CONSTRUCTOR,  //构造器
    LOCAL_VARIABLE,  //局部变量
    ANNOTATION_TYPE,  //注解类
    PACKAGE,  //包
    TYPE_PARAMETER,  //类型参数
    TYPE_USE  //任何类型
}

在该类中,我已经将每个值所代表的意思以注释标注(下面有类似的以同样方式处理),在@SpringBootApplication注解上标注的Target注解取值为ElementType.TYPE,说明@SpringBootApplication注解只能作用在类级元素上。

@Retention

@Retention是java元注解,用于定义注解类被保留的时间长短。该注解内部只有一个枚举类RetentionPolicy的变量value,说明该注解取值只能为该枚举类中的一个值,该枚举类如下:

public enum RetentionPolicy {
    SOURCE,  //源文件
    CLASS,  //class文件
    RUNTIME  //运行时
}

@SpringBootApplication注解上标注的@Retention注解的取值为RetentionPolicy.RUNTIME,说明使用@SpringBootApplication注解的类在运行时有效。

@Documented

@Documented是java的元注解,它没有成员,其作用就是表明当前的注解会被javadoc工具记录(javadoc中默认是不记录注解的)。

@Inherited

该注解也是java元注解,它的作用是表明被@Inherited标注的注解是可以被继承的。所谓可以被继承什么意思?我们就以@SpringBootApplication来说明。@SpringBootApplication注解被定义为可以继承的注解,目前在主程序类MainApplication上使用了这个类注解,那么MainApplication有一个子类ChildApplication未使用注解@SpringBootApplication,当他要查找这个注解时,会自动向其父类查找,未找到时继续向父级查找,直到找到或返回错误为止。

下面三个注解是@SpringBootApplication组合注解中的重要注解,在主程序类上以三者注解来代替@SpringBootApplication注解也可以启动成功。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
//@SpringBootApplication
public class HelloWorldMainApplication {
    public static void main(String[] args) {
        //启动SpringBoot应用
        SpringApplication.run(HelloWorldMainApplication.class,args);
    }
}

下面我们具体来看看这三个注解分别有什么作用。

@SpringBootConfiguration

这个注解是SpringBoot中特有的注解,通过查看其源码可以可知,SpringBootConfiguration实际上就是一个@Configuration注解的简单封装,两者的作用均是将当前类标注为一个配置类。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

在@Configuration中还有三个较为常用的注解:

@Bean:作用在方法或类上,用于生成bean,并加入到IOC容器中。在@Configuration注解主要是作用在方法上,将配置的bean对象加入到容器中。

@Import:引入其他的配置类

@ImportResource:引入配置文件

@EnableAutoConfiguration

这个注解是SpringBoot中新加入的注解,它的作用是开启自动装配,SpringBoot的易上手性很大一部分就是归功于这个注解,下面我们来具体了解一下。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

在这个注解中有两个重要的点:

@AutoConfigurationPackage

这个是一个自动配置包注解,源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

它最关键的点就在于引入了AutoConfigurationPackages.Registrar类,这个类的registerBeanDefinitions方法会得到@SpringBootApplication注解标注的类所在的包,具体调试可以查看。
总结一下:@AutoConfigurationPackage注释的作用就是将主配置类所在的包下面所有的组件都扫描到Spring容器中。

@Import(EnableAutoConfigurationImportSelector.class)

另外@EnableAutoConfiguration还引入了EnableAutoConfigurationImportSelector类。这个类是ImportSelector接口的实现,其中的selectImports方法为其核心方法,大概的逻辑为:

1. 加载配置文件META-INF/spring.factories,从其中加载所有可能用到的配置类;

2. 去重,排序,并且去掉exclude和excludeName匹配成功的类;

  1. 依据条件匹配进行过滤,将满足条件的自动装配类返回。

具体的内容在https://blog.51cto.com/5880861/1970542中讲解比较详细

@ComponentScan

这个注解是Spring中一个重要的注解,我们知道在Spring中有四种定义bean的注解,分别为:@Component、@Controller、@Service、@Repository,但是仅仅依赖这些注解的作用是无法在IOC容器中生成bean对象的,因为Spring并不知道哪些类上面标注这些注解,此时就需要使用@ComponentScan来定义要扫描的范围,Spring会在@ComponentScan定义的范围内扫描被这些注解的标识的类,最终生成bean并放入IOC容器中。

@ComponentScan注解的参数basePackages和value互为别名,用来定义要扫描的范围,即包名,扫描的范围就是定义的包中的类以及子包中的类,在未指定参数是,默认是扫描当前类所在的包及其子包的类。参数excludeFilters用于排除一些满足过滤器定义条件的类,它是一个数组类型,说明可以指定多个不扫描的过滤器规则。

在@SpringBootApplication中的@ComponentScan未定义扫描的范围参数,默认扫描的是主程序类所在的包以及子包下的类,因此为了能使程序中的类都能被扫描到,我们应当将主程序类定义在根路径下(官方建议为root package,含义相同),这样项目所需要用到的类都在主程序类所在的包和子包下,就不会存在有bean对象未被扫描的情况。

@SpringBootApplication是有定义排除规则的

@ComponentScan(excludeFilters = {
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

至于为什么要用到TypeExcludeFilter.classAutoConfigurationExcludeFilter.class 这两个过滤器,我的理解有限,可以移步到→https://www.imooc.com/article/293077

欢迎补充和指正🙂