精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解

该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读

Spring Boot 版本:2.2.x

最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章导读》 系列文章

如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~

该系列其他文章请查看:《精尽 Spring Boot 源码分析 - 文章导读》

概述

现如今,Spring Boot 在许多中大型企业中被普及,想必大家对于 @SpringBootApplication 并不陌生,这个注解通常标注在我们应用的启动类上面,标记是一个 Spring Boot 应用,同时开启自动配置的功能,那么你是否有深入了解过该注解呢?没有的话,或许这篇文章可以让你对它有一个新的认识。

提示:@EnableAutoConfiguration 是开启自动配置功能的模块驱动注解,是 Spring Boot 的核心注解

整篇文章主要是对这个注解,也就是 Spring Boot 的自动配置功能进行展述

@SpringBootApplication

org.springframework.boot.autoconfigure.SpringBootApplication 注解在 Spring Boot 的 spring-boot-autoconfigre 子模块下,当我们引入 spring-boot-starter 模块后会自动引入该子模块

该注解是一个组合注解,如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited // 表明该注解定义在某个类上时,其子类会继承该注解
@SpringBootConfiguration // 继承 `@Configuration` 注解
@EnableAutoConfiguration // 开启自动配置功能
// 扫描指定路径下的 Bean
@ComponentScan( excludeFilters = {
    			// 默认没有 TypeExcludeFilter
				@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    			// 排除掉自动配置类
				@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
	/**
	 * 需要自动配置的 Class 类
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * 需要自动配置的类名称
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	/**
	 * 需要扫描的路径
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	/**
	 * 需要扫描的 Class 类
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

	/**
	 * 被标记的 Bean 是否进行 CGLIB 提升
	 */
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

@SpringBootApplication 注解就是一个组合注解,里面的每个配置都是元注解中对应的属性,上面已做描述

该注解上面的 @Inherited 元注解是 Java 提供的,标注后表示当前注解定义在某个类上时,其子类会继承该注解,我们一起来看看其他三个注解

@SpringBootConfiguration

org.springframework.boot.SpringBootConfiguration 注解,Spring Boot 自定义注解

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

    /**
	 * 被标记的 Bean 是否进行 CGLIB 提升
	 */
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

该注解很简单,上面标注了 @Configuration 元注解,所以作用相同,同样是将一个类标注为配置类,能够作为一个 Bean 被 Spring IoC 容器管理

至于为什么不直接使用 @Configuration 注解呢,我想这应该是 领域驱动设计 中的一种思想,可以使得 Spring Boot 更加灵活,总有它的用武之地

领域驱动设计:Domain-Driven Design,简称 DDD。过去系统分析和系统设计都是分离的,这样割裂的结果导致需求分析的结果无法直接进行设计编程,而能够进行编程运行的代码却扭曲需求,导致客户运行软件后才发现很多功能不是自己想要的,而且软件不能快速跟随需求变化。DDD 则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。

@ComponentScan

org.springframework.context.annotation.ComponentScan 注解,Spring 注解,扫描指定路径下的标有 @Component 注解的类,解析成 Bean 被 Spring IoC 容器管理

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

    /**
     * 指定的扫描的路径
     */
	@AliasFor("basePackages")
	String[] value() default {};

    /**
     * 指定的扫描的路径
     */
	@AliasFor("value")
	String[] basePackages() default {};

    /**
     * 指定的扫描的 Class 对象
     */
	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 ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

	boolean useDefaultFilters() default true;

    /**
     * 扫描时的包含过滤器
     */
	Filter[] includeFilters() default {};
	/**
     * 扫描时的排除过滤器
     */
	Filter[] excludeFilters() default {};

	boolean lazyInit() default false;
}

想深入了解该注解的小伙伴可以查看我前面对 Spring IoC 进行源码分析的文章:

该注解通常需要和 @Configuration 注解一起使用,因为需要先被当做一个配置类,然后解析到上面有 @ComponentScan 注解后则处理该注解,通过 ClassPathBeanDefinitionScanner 扫描器去扫描指定路径下标注了 @Component 注解的类,将他们解析成 BeanDefinition(Bean 的前身),后续则会生成对应的 Bean 被 Spring IoC 容器管理

当然,如果该注解没有通过 basePackages 指定路径,Spring 会选在以该注解标注的类所在的包作为基础路径,然后扫描包下面的这些类

@EnableAutoConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration 注解,Spring Boot 自定义注解,用于驱动 Spring Boot 自动配置模块

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 注册一个 Bean 保存当前注解标注的类所在包路径
@Import(AutoConfigurationImportSelector.class) // Spring Boot 自动配置的实现
public @interface EnableAutoConfiguration {
	/**
	 * 可通过这个配置关闭 Spring Boot 的自动配置功能
	 */
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * 指定需要排除的自动配置类的 Class 对象
	 */
	Class<?>[] exclude() default {};

	/**
	 * 指定需要排除的自动配置类的名称
	 */
	String[] excludeName() default {};
}

对于 Spring 中的模块驱动注解的实现都是通过 @Import 注解来实现的

模块驱动注解通常需要结合 @Configuration 注解一起使用,因为需要先被当做一个配置类,然后解析到上面有 @Import 注解后则进行处理,对于 @Import 注解的值有三种情况:

  1. 该 Class 对象实现了 ImportSelector 接口,调用它的 selectImports(..) 方法获取需要被处理的 Class 对象的名称,也就是可以将它们作为一个 Bean 被 Spring IoC 管理

    • 该 Class 对象实现了 DeferredImportSelector 接口,和上者的执行时机不同,在所有配置类处理完后再执行,且支持 @Order 排序
  2. 该 Class 对象实现了 ImportBeanDefinitionRegistrar 接口,会调用它的 registerBeanDefinitions(..) 方法,自定义地往 BeanDefinitionRegistry 注册中心注册 BeanDefinition(Bean 的前身)

  3. 该 Class 对象是一个 @Configuration 配置类,会将这个类作为一个 Bean 被 Spring IoC 管理

对于 @Import 注解不熟悉的小伙伴可查看我前面的 《死磕Spring之IoC篇 - @Bean 等注解的实现原理》 这篇文章

这里的 @EnableAutoConfiguration 自动配置模块驱动注解,通过 @Import 导入 AutoConfigurationImportSelector 这个类(实现了 DeferredImportSelector 接口)来驱动 Spring Boot 的自动配置模块,下面会进行分析

@AutoConfigurationPackage

我们注意到 @EnableAutoConfiguration 注解上面还有一个 @AutoConfigurationPackage 元注解,它的作用就是注册一个 Bean,保存了当前注解标注的类所在包路径

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
/**
 * 将当前注解所标注的类所在包名封装成一个 {@link org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages} 进行注册
 * 例如 JPA 模块的会使用到这个对象(JPA entity scanner)
 */
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage { }

同样这里使用了 @Import 注解来实现的,对应的是一个 AutoConfigurationPackages.Registrar 内部类,如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 注册一个 BasePackages 类的 BeanDefinition,角色为内部角色,名称为 `org.springframework.boot.autoconfigure.AutoConfigurationPackages`
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        // 将注解元信息封装成 PackageImport 对象,对注解所在的包名进行封装
        return Collections.singleton(new PackageImport(metadata));
    }
}

比较简单,这里直接跳过了

自动配置

在开始之前,我们先来了解一下 Spring Boot 的自动配置,就是通过引入某个功能的相关 jar 包依赖后,Spring Boot 能够自动配置应用程序,让我们很方便的使用该功能

  • 例如当你引入 spring-boot-starter-aop 后,会自动引入 AOP 相关的 jar 包依赖,那么在 spring-boot-autoconfigure 中有一个 AopAutoConfiguration 自动配置类会自动驱动整个 AOP 模块

  • 例如当你引入 spring-boot-starter-web 后,会自动引入 Spring MVC、Tomcat 相关的 jar 包依赖,那么在 spring-boot-autoconfigure 中会有相应的自动配置类会自动配置 Spring MVC

当然,还有许多自动配置类,结合这 Spring Boot 的 Starter 模块,让许多功能或者第三方 jar 包能够很简便的和 Spring Boot 整合在一起使用

现在很多开源框架都提供了对应的 Spring Boot Starter 模块,能够更好的整合 Spring Boot,当你熟悉自动配置功能后,你也可以很轻松的写一个 Starter 包供他人使用

上一篇:git1-学习笔记


下一篇:SpringBoot成长记8:SpringBoot如何实现自动装配配置和扩展