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


参考 知识星球 中 芋道源码 星球的源码解析,一个活跃度非常高的 Java 技术社群,感兴趣的小伙伴可以加入 芋道源码 星球,一起学习😄

该系列文章是笔者在学习 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 包供他人使用😃😃😃

这里先提前剧透一下,自动配置类为什么在你引入相关 jar 包后会自动配置对应的模块呢?

主要就是拓展了 Spring 的 Condition,例如 @ConditionalOnClass 注解,当存在指定的 Class 对象时才注入某个 Bean

同时也可以再结合 @EnableXxx 模块注解,通过 @Import 注解驱动某个模块

具体细节,请继续往下看

AutoConfigurationImportSelector

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector,实现了 DeferredImportSelector 接口,是 @EnableAutoConfiguration 注解驱动自动配置模块的核心类

直接看到实现的 ImportSelector 接口的方法

1. selectImports 方法

selectImports(AnnotationMetadata) 方法,返回需要注入的 Bean 的类名称

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // <1> 如果通过 `spring.boot.enableautoconfiguration` 配置关闭了自动配置功能
    if (!isEnabled(annotationMetadata)) {
        // 返回一个空数组
        return NO_IMPORTS;
    }
    /**
     * <2> 解析 `META-INF/spring-autoconfigure-metadata.properties` 文件,生成一个 AutoConfigurationMetadata 自动配置类元数据对象
     *
     * 说明:引入 `spring-boot-autoconfigure-processor` 工具模块依赖后,其中会通过 Java SPI 机制引入 {@link AutoConfigureAnnotationProcessor} 注解处理器在编译阶段进行相关处理
     * 其中 `spring-boot-autoconfigure` 模块会引入该工具模块(不具有传递性),那么 Spring Boot 在编译 `spring-boot-autoconfigure` 这个 `jar` 包的时候,
     * 在编译阶段会扫描到带有 `@ConditionalOnClass` 等注解的 `.class` 文件,也就是自动配置类,将自动配置类的信息保存至 `META-INF/spring-autoconfigure-metadata.properties` 文件中
     * 例如保存类 `自动配置类类名.注解简称` => `注解中的值(逗号分隔)` 和 `自动配置类类名` => `空字符串`
     *
     * 当然,你自己写的 Spring Boot Starter 中的自动配置模块也可以引入这个 Spring Boot 提供的插件
     */
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    // <3> 从所有的 `META-INF/spring.factories` 文件中找到 `@EnableAutoConfiguration` 注解对应的类(需要自动配置的类)
    // 会进行过滤处理,然后封装在一个对象中
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
    // <4> 返回所有需要自动配置的类
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

过程如下:

  1. 如果通过 spring.boot.enableautoconfiguration 配置关闭了自动配置功能,那么直接返回一个空数组
  2. 解析 META-INF/spring-autoconfigure-metadata.properties 文件,生成一个 AutoConfigurationMetadata 自动配置类元数据对象
  3. 调用 getAutoConfigurationEntry(..) 方法, 从所有的 META-INF/spring.factories 文件中找到 @EnableAutoConfiguration 注解对应的类(需要自动配置的类),会进行过滤处理,然后封装在一个 AutoConfigurationEntry 对象中
  4. 返回所有需要自动配置的类

上面第 2 步调用的方法:

final class AutoConfigurationMetadataLoader {

	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

	private AutoConfigurationMetadataLoader() {
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			// <1> 获取所有 `META-INF/spring-autoconfigure-metadata.properties` 文件 URL
			Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path);
			Properties properties = new Properties();
			// <2> 加载这些文件并将他们的属性添加到 Properties 中
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
			}
			// <3> 将这个 Properties 封装到 PropertiesAutoConfigurationMetadata 对象中并返回
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

	static AutoConfigurationMetadata loadMetadata(Properties properties) {
		return new PropertiesAutoConfigurationMetadata(properties);
	}
}

这一步困惑了我很久,因为在 Spring Boot 工程中根本找不到 META-INF/spring-autoconfigure-metadata.properties 文件,而我们自己也没有配置过,但是在我们自己的 Spring Boot 应用依赖的 spring-boot-autoconfigure.jar 包里面又存在这个文件,如下:

那么这是为什么呢?经过我长时间的 Google,找到了答案

  1. 在引入 spring-boot-autoconfigure-processor 工具模块依赖后,其中会通过 Java SPI 机制引入 AutoConfigureAnnotationProcessor 注解处理器在编译阶段进行相关处理

  2. 其中 spring-boot-autoconfigure 模块会引入该工具模块(不具有传递性),那么 Spring Boot 在编译 spring-boot-autoconfigure 这个 jar 包的时候,在编译阶段会扫描到带有 @ConditionalOnClass 等注解的 .class 文件,也就是自动配置类,然后将自动配置类的一些信息保存至 META-INF/spring-autoconfigure-metadata.properties 文件中

  3. 文件中保存了 自动配置类类名.注解简称 --> 注解中的值(逗号分隔),自动配置类类名 --> 空字符串

  4. 当然,你自己写的 Spring Boot Starter 中的自动配置模块也可以引入这个 Spring Boot 提供的插件

得到的结论:

至于为什么这么做,是因为 Spring Boot 提供的自动配置类比较多,而我们不可能使用到很多自动配置功能,大部分都没必要,如果每次你启动应用的过程中,都需要一个一个去解析他们上面的 Conditional 注解,那么肯定会有不少的性能损耗

这里,Spring Boot 做了一个优化,通过自己提供的工具,在编译阶段将自动配置类的一些注解信息保存在一个 properties 文件中,这样一来,在你启动应用的过程中,就可以直接读取该文件中的信息,提前过滤掉一些自动配置类,相比于每次都去解析它们所有的注解,性能提升不少

2. getAutoConfigurationEntry 方法

getAutoConfigurationEntry(AutoConfigurationMetadata, AnnotationMetadata) 方法,返回符合条件的自动配置类,如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    // <1> 如果通过 `spring.boot.enableautoconfiguration` 配置关闭了自动配置功能
    if (!isEnabled(annotationMetadata)) {
        // 则返回一个“空”的对象
        return EMPTY_ENTRY;
    }
    // <2> 获取 `@EnableAutoConfiguration` 注解的配置信息
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // <3> 从所有的 `META-INF/spring.factories` 文件中找到 `@EnableAutoConfiguration` 注解对应的类(需要自动配置的类)
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // <4> 对所有的自动配置类进行去重
    configurations = removeDuplicates(configurations);
    // <5> 获取需要排除的自动配置类
    // 可通过 `@EnableAutoConfiguration` 注解的 `exclude` 和 `excludeName` 配置
    // 也可以通过 `spring.autoconfigure.exclude` 配置
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // <6> 处理 `exclusions` 中特殊的类名称,保证能够排除它
    checkExcludedClasses(configurations, exclusions);
    // <7> 从 `configurations` 中将 `exclusions` 需要排除的自动配置类移除
    configurations.removeAll(exclusions);
    /**
     * <8> 从 `META-INF/spring.factories` 找到所有的 {@link AutoConfigurationImportFilter} 对 `configurations` 进行过滤处理
     * 例如 Spring Boot 中配置了 {@link org.springframework.boot.autoconfigure.condition.OnClassCondition}
     * 在这里提前过滤掉一些不满足条件的自动配置类,在 Spring 注入 Bean 的时候也会判断哦~
     */
    configurations = filter(configurations, autoConfigurationMetadata);
    /**
     * <9> 从 `META-INF/spring.factories` 找到所有的 {@link AutoConfigurationImportListener} 事件监听器
     * 触发每个监听器去处理 {@link AutoConfigurationImportEvent} 事件,该事件中包含了 `configurations` 和 `exclusions`
     * Spring Boot 中配置了一个 {@link org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener}
     * 目的就是将 `configurations` 和 `exclusions` 保存至 {@link AutoConfigurationImportEvent} 对象中,并注册到 IoC 容器中,名称为 `autoConfigurationReport`
     * 这样一来我们可以注入这个 Bean 获取到自动配置类信息
     */
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // <10> 将所有的自动配置类封装成一个 AutoConfigurationEntry 对象,并返回
    return new AutoConfigurationEntry(configurations, exclusions);
}

过程如下:

  1. 如果通过 spring.boot.enableautoconfiguration 配置关闭了自动配置功能,则返回一个“空”的对象

  2. 获取 @EnableAutoConfiguration 注解的配置信息

  3. 从所有的 META-INF/spring.factories 文件中找到 @EnableAutoConfiguration 注解对应的类(需要自动配置的类),保存在 configurations 集合中

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        // 从所有的 `META-INF/spring.factories` 文件中找到 `@EnableAutoConfiguration` 注解对应的类(需要自动配置的类)
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        // 如果为空则抛出异常
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
    

    这个 SpringFactoriesLoader 是由 Spring 提供的一个类

  4. 对所有的自动配置类进行去重

    protected final <T> List<T> removeDuplicates(List<T> list) {
        return new ArrayList<>(new LinkedHashSet<>(list));
    }
    
  5. 获取需要排除的自动配置类,可通过 @EnableAutoConfiguration 注解的 exclude 和 excludeName 配置,也可以通过 spring.autoconfigure.exclude 配置

  6. 处理 exclusions 中特殊的类名称,保证能够排除它

  7. 从 configurations 中将 exclusions 需要排除的自动配置类移除

  8. 调用 filter(..) 方法, 目的就是过滤掉一些不符合 Condition 条件的自动配置类,和在 1. selectImports 方法 小节中讲到的性能优化有关哦

  9. 从 META-INF/spring.factories 找到所有的 AutoConfigurationImportListener 事件监听器,触发每个监听器去处理 AutoConfigurationImportEvent 事件,该事件中包含了 configurations 和 exclusions

    Spring Boot 中配置了一个监听器,目的就是将 configurations 和 exclusions 保存至 AutoConfigurationImportEvent 对象中,并注册到 IoC 容器中,名称为 autoConfigurationReport,这样一来我们可以注入这个 Bean 获取到自动配置类信息

  10. 将所有的自动配置类封装成一个 AutoConfigurationEntry 对象,并返回

整个过程不复杂,关键在于上面的第 3 步和第 8 步,先从所有的 META-INF/spring.factories 文件中找到 @EnableAutoConfiguration 注解对应的类(需要自动配置的类),然后进行过滤

3. filter 方法

filter(List<String>, AutoConfigurationMetadata) 方法,过滤一些自动配置类

我们得先知道这两个入参:

  1. 所有的自动配置类名称
  2. META-INF/spring-autoconfigure-metadata.properties 文件保存的 Spring Boot 的自动配置类的注解元信息(Sprng Boot 编译时生成的)

这里的目的就是根据 2 里面的注解元信息,先过滤掉一些自动配置类

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    // <1> 将自动配置类保存至 `candidates` 数组中
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    /*
     * <2> 从 `META-INF/spring.factories` 找到所有的 AutoConfigurationImportFilter 对 `candidates` 进行过滤处理
     * 有 OnClassCondition、OnBeanCondition、OnWebApplicationCondition
     */
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        // <2.1> Aware 回调
        invokeAwareMethods(filter);
        // <2.2> 对 `candidates` 进行匹配处理,获取所有的匹配结果
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        // <2.3> 遍历匹配结果,将不匹配的自动配置类至空
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                skip[i] = true;
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    // <3> 如果没有不匹配的结果则全部返回
    if (!skipped) {
        return configurations;
    }
    // <4> 获取到所有匹配的自动配置类,并返回
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    return new ArrayList<>(result);
}

过程如下:

  1. 将自动配置类保存至 candidates 数组中

  2. 从 META-INF/spring.factories 找到所有的 AutoConfigurationImportFilter 对 candidates 进行过滤处理

    # Auto Configuration Import Filters
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
    org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
    org.springframework.boot.autoconfigure.condition.OnClassCondition,\
    org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
    
    1. Aware 回调
    2. 对 candidates 进行匹配处理,获取所有的匹配结果,注意,这里传入了 AutoConfigurationMetadata 对象
    3. 遍历匹配结果,将不匹配的自动配置类至空
  3. 如果没有不匹配的结果则全部返回

  4. 获取到所有匹配的自动配置类,并返回

关键在于上面的第 2 步,通过 Spring Boot 自己扩展的几个自动配置类过滤器进行过滤,由于这部分内容和 Spring Boot 拓展 Condition 相关,放入下篇文章进行分析

下面我们一起来看看上面 1. selectImports 方法 小节中讲到的性能优化,META-INF/spring-autoconfigure-metadata.properties 文件是如何生成的,文件的内容又是什么

AutoConfigureAnnotationProcessor

org.springframework.boot.autoconfigureprocessor.AutoConfigureAnnotationProcessor,Spring Boot 的 spring-boot-autoconfigure-processor 工具模块中的自动配置类的注解处理器,在编译阶段扫描自动配置类的注解元信息,并将他们保存至一个 properties 文件中

@SupportedAnnotationTypes({ "org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
		"org.springframework.boot.autoconfigure.condition.ConditionalOnBean",
		"org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate",
		"org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication",
		"org.springframework.boot.autoconfigure.AutoConfigureBefore",
		"org.springframework.boot.autoconfigure.AutoConfigureAfter",
		"org.springframework.boot.autoconfigure.AutoConfigureOrder" })
public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
	/**
	 * 生成的文件
	 */
	protected static final String PROPERTIES_PATH = "META-INF/spring-autoconfigure-metadata.properties";
	/**
	 * 保存指定注解的简称和注解全称之间的对应关系(不可修改)
	 */
	private final Map<String, String> annotations;

	private final Map<String, ValueExtractor> valueExtractors;

	private final Properties properties = new Properties();

	public AutoConfigureAnnotationProcessor() {
		// <1> 创建一个 Map 集合
		Map<String, String> annotations = new LinkedHashMap<>();
		// <1.1> 将指定注解的简称和全称之间的对应关系保存至第 `1` 步创建的 Map 中
		addAnnotations(annotations);
		// <1.2> 将 `1.1` 的 Map 转换成不可修改的 UnmodifiableMap 集合,赋值给 `annotations`
		this.annotations = Collections.unmodifiableMap(annotations);
		// <2> 创建一个 Map 集合
		Map<String, ValueExtractor> valueExtractors = new LinkedHashMap<>();
		// <2.1> 将指定注解的简称和对应的 ValueExtractor 对象保存至第 `2` 步创建的 Map 中
		addValueExtractors(valueExtractors);
		// <2.2> 将 `2.1` 的 Map 转换成不可修改的 UnmodifiableMap 集合,赋值给 `valueExtractors`
		this.valueExtractors = Collections.unmodifiableMap(valueExtractors);
	}
}

AbstractProcessor 是 JDK 1.6 引入的一个抽象类,支持在编译阶段进行处理,在构造器中做了以下事情:

  1. 创建一个 Map 集合

    1. 将指定注解的简称和全称之间的对应关系保存至第 1 步创建的 Map 中

      protected void addAnnotations(Map<String, String> annotations) {
          annotations.put("ConditionalOnClass", "org.springframework.boot.autoconfigure.condition.ConditionalOnClass");
          annotations.put("ConditionalOnBean", "org.springframework.boot.autoconfigure.condition.ConditionalOnBean");
          annotations.put("ConditionalOnSingleCandidate", "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate");
          annotations.put("ConditionalOnWebApplication", "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication");
          annotations.put("AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureBefore");
          annotations.put("AutoConfigureAfter", "org.springframework.boot.autoconfigure.AutoConfigureAfter");
          annotations.put("AutoConfigureOrder", "org.springframework.boot.autoconfigure.AutoConfigureOrder");
      }
      
    2. 将 1.1 的 Map 转换成不可修改的 UnmodifiableMap 集合,赋值给 annotations

  2. 创建一个 Map 集合

    1. 将指定注解的简称和对应的 ValueExtractor 对象保存至第 2 步创建的 Map 中

      private void addValueExtractors(Map<String, ValueExtractor> attributes) {
          attributes.put("ConditionalOnClass", new OnClassConditionValueExtractor());
          attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor());
          attributes.put("ConditionalOnSingleCandidate", new OnBeanConditionValueExtractor());
          attributes.put("ConditionalOnWebApplication", ValueExtractor.allFrom("type"));
          attributes.put("AutoConfigureBefore", ValueExtractor.allFrom("value", "name"));
          attributes.put("AutoConfigureAfter", ValueExtractor.allFrom("value", "name"));
          attributes.put("AutoConfigureOrder", ValueExtractor.allFrom("value"));
      }
      
    2. 将 2.1 的 Map 转换成不可修改的 UnmodifiableMap 集合,赋值给 valueExtractors

getSupportedSourceVersion 方法

返回支持的 Java 版本

@Override
public SourceVersion getSupportedSourceVersion() {
    // 返回 Java 版本,默认 1.5
    return SourceVersion.latestSupported();
}

process 方法

处理过程

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    // <1> 遍历上面的几个 `@Conditional` 注解和几个定义自动配置类顺序的注解,依次进行处理
    for (Map.Entry<String, String> entry : this.annotations.entrySet()) {
        // <1.1> 对支持的注解进行处理,也就是找到所有标注了该注解的类,然后解析出该注解的值,保存至 Properties
        // 例如 `类名.注解简称` => `注解中的值(逗号分隔)` 和 `类名` => `空字符串`,将自动配置类的信息已经对应注解的信息都保存起来
        // 避免你每次启动 Spring Boot 应用都要去解析自动配置类上面的注解,是引入 `spring-boot-autoconfigure` 后可以从 `META-INF/spring-autoconfigure-metadata.properties` 文件中直接获取
        // 这么一想,Spring Boot 设计的太棒了,所以你自己写的 Spring Boot Starter 中的自动配置模块也可以引入这个 Spring Boot 提供的插件
        process(roundEnv, entry.getKey(), entry.getValue());
    }
    // <2> 如果处理完成
    if (roundEnv.processingOver()) {
        try {
            // <2.1> 将 Properties 写入 `META-INF/spring-autoconfigure-metadata.properties` 文件
            writeProperties();
        }
        catch (Exception ex) {
            throw new IllegalStateException("Failed to write metadata", ex);
        }
    }
    // <3> 返回 `false`
    return false;
}

过程如下:

  1. 遍历上面的几个 @Conditional 注解和几个定义自动配置类顺序的注解,依次进行处理

    1. 调用 process(..) 重载方法,对支持的注解进行处理,也就是找到所有标注了该注解的类,然后解析出该注解的值,保存至 Properties
  2. 如果处理完成

    1. 调用 writeProperties() 方法,将 Properties 写入 META-INF/spring-autoconfigure-metadata.properties 文件

      private void writeProperties() throws IOException {
          if (!this.properties.isEmpty()) {
              FileObject file = this.processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH);
              try (OutputStream outputStream = file.openOutputStream()) {
                  this.properties.store(outputStream, null);
              }
          }
      }
      

上面的第 1.1 步处理后的 Properties 包含以下内容:

  • 自动配置类的类名.注解简称 --> 注解中的值(逗号分隔)
  • 自动配置类的类名 --> 空字符串

通过后续写入的文件,避免你每次启动 Spring Boot 应用都要去解析自动配置类上面的注解,从而提高应用启动时的效率

这么一想,Spring Boot 设计的太棒了,所以你自己写的 Spring Boot Starter 中的自动配置模块也可以引入这个 Spring Boot 提供的插件

process 重载方法

private void process(RoundEnvironment roundEnv, String propertyKey, String annotationName) {
    // <1> 获取到这个注解名称对应的 Java 类型
    TypeElement annotationType = this.processingEnv.getElementUtils().getTypeElement(annotationName);
    if (annotationType != null) {
        // <2> 如果存在该注解,则从 RoundEnvironment 中获取标注了该注解的所有 Element 元素,进行遍历
        for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
            // <2.1> 获取这个 Element 元素 innermost 最深处的 Element
            Element enclosingElement = element.getEnclosingElement();
            // <2.2> 如果最深处的 Element 的类型是 PACKAGE 包,那么表示这个元素是一个类,则进行处理
            if (enclosingElement != null && enclosingElement.getKind() == ElementKind.PACKAGE) {
                // <2.2.1> 解析这个类上面 `annotationName` 注解的信息,并保存至 `properties` 中
                processElement(element, propertyKey, annotationName);
            }
        }
    }
}

过程如下:

  1. 获取到这个注解名称对应的 Java 类型
  2. 如果存在该注解,则从 RoundEnvironment 中获取标注了该注解的所有 Element 元素,进行遍历
    1. 获取这个 Element 元素 innermost 最深处的 Element
    2. 如果最深处的 Element 的类型是 PACKAGE 包,那么表示这个元素是一个类,则进行处理
      1. 调用 processElement(..) 方法,解析这个类上面 annotationName 注解的信息,并保存至 properties 中

processElement 方法

private void processElement(Element element, String propertyKey, String annotationName) {
    try {
        // <1> 获取这个类的名称
        String qualifiedName = Elements.getQualifiedName(element);
        // <2> 获取这个类上面的 `annotationName` 类型的注解信息
        AnnotationMirror annotation = getAnnotation(element, annotationName);
        if (qualifiedName != null && annotation != null) {
            // <3> 获取这个注解中的值
            List<Object> values = getValues(propertyKey, annotation);
            // <4> 往 `properties` 中添加 `类名.注解简称` => `注解中的值(逗号分隔)`
            this.properties.put(qualifiedName + "." + propertyKey, toCommaDelimitedString(values));
            // <5> 往 `properties` 中添加 `类名` => `空字符串`
            this.properties.put(qualifiedName, "");
        }
    }
    catch (Exception ex) {
        throw new IllegalStateException("Error processing configuration meta-data on " + element, ex);
    }
}

过程如下:

  1. 获取这个类的名称

  2. 获取这个类上面的 annotationName 类型的注解信息

    private AnnotationMirror getAnnotation(Element element, String type) {
        if (element != null) {
            for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
                if (type.equals(annotation.getAnnotationType().toString())) {
                    return annotation;
                }
            }
        }
        return null;
    }
    
  3. 获取这个注解中的值

    private List<Object> getValues(String propertyKey, AnnotationMirror annotation) {
        // 获取该注解对应的 value 抽取器
        ValueExtractor extractor = this.valueExtractors.get(propertyKey);
        if (extractor == null) {
            return Collections.emptyList();
        }
        // 获取这个注解中的值,并返回
        return extractor.getValues(annotation);
    }
    
  4. 往 properties 中添加 类名.注解简称 --> 注解中的值(逗号分隔)

  5. 往 properties 中添加 类名 --> 空字符串

总结

本文分析了 @SpringBootApplication 组合注解,它是 @SpringBootConfiguration、@ComponentScan 和 @EnableAutoConfiguration 几个注解的组合注解,对于前两个注解我想你并不陌生,分析 Spring 源码的时候差不多已经讲过,最后一个注解则是 Spring Boot 自动配置功能的驱动注解,也是本文讲述的一个重点。

@EnableAutoConfiguration 注解的实现原理并不复杂,借助于 @Import 注解,从所有 META-INF/spring.factories 文件中找到 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的值,例如:

会将这些自动配置类作为一个 Bean 尝试注入到 Spring IoC 容器中,注入的时候 Spring 会通过 @Conditional 注解判断是否符合条件,因为并不是所有的自动配置类都满足条件。当然,Spring Boot 对 @Conditional 注解进行了扩展,例如 @ConditionalOnClass 可以指定必须存在哪些 Class 对象才注入这个 Bean。

Spring Boot 会借助 spring-boot-autoconfigure-processor 工具模块在编译阶段将自己的自动配置类的注解元信息保存至一个 properties 文件中,避免每次启动应用都要去解析这么多自动配置类上面的注解。同时会通过几个过滤器根据这个 properties 文件过滤掉一些自动配置类,具体怎么过滤的会在下面文章讲到。

好了,总结下来就是 Spring Boot 会从 META-INF/spring.factories 文件中找到配置的自动配置类,然后根据 Condition 条件进行注入,如果注入的话则可以通过 @EnableXxx 模块驱动注解驱动某个模块,例如 Spring AOP 模块。那么关于 Spring Boot 对 Spring Condition 的扩展在下篇文章进行分析。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2026 CODEPRJ.COM