跳至主要內容

5.剖析 @SpringBootApplication 注解

Java突击队大约 23 分钟

5.剖析 @SpringBootApplication 注解

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

Spring Boot 版本:2.2.x

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

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

该系列其他文章请查看:《死磕 Spring Boot 源码分析 - 文章导读》open in new window

概述

现如今,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被SpringIoC管理;

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

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

这里的@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工具模块依赖后,其中会通过JavaSPI机制引入AutoConfigureAnnotationProcessor注解处理器在编译阶段进行相关处理;
2、 其中spring-boot-autoconfigure模块会引入该工具模块(不具有传递性),那么SpringBoot在编译spring-boot-autoconfigure这个jar包的时候,在编译阶段会扫描到带有@ConditionalOnClass等注解的.class文件,也就是自动配置类,然后将自动配置类的一些信息保存至META-INF/spring-autoconfigure-metadata.properties文件中;
3、 文件中保存了自动配置类类名.注解简称-->注解中的值(逗号分隔)自动配置类类名-->空字符串
4、 当然,你自己写的SpringBootStarter中的自动配置模块也可以引入这个SpringBoot提供的插件;

得到的结论:

至于为什么这么做,是因为 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注解的excludeexcludeName配置,也可以通过spring.autoconfigure.exclude配置;
6、 处理exclusions中特殊的类名称,保证能够排除它;
7、configurations中将exclusions需要排除的自动配置类移除;
8、 调用filter(..)方法,目的就是过滤掉一些不符合Condition条件的自动配置类,和在1.selectImports方法小节中讲到的性能优化有关哦;
9、META-INF/spring.factories找到所有的AutoConfigurationImportListener事件监听器,触发每个监听器去处理AutoConfigurationImportEvent事件,该事件中包含了configurationsexclusions

Spring Boot 中配置了一个监听器,目的就是将 configurationsexclusions 保存至 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文件保存的SpringBoot的自动配置类的注解元信息(SprngBoot编译时生成的);

这里的目的就是根据 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找到所有的AutoConfigurationImportFiltercandidates进行过滤处理;

# 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中;

    ```java 
    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中;  
    
        ```java 
        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文件;

    ```java 
    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 重载方法 ####

```java 
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 的扩展在下篇文章进行分析。

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址:open in new window