spring 自动加载配置

0. 前言

  • springboot有着开箱即用的特性,mybatis等组件只需要用maven安装好,springboot就会自动组装,不需要自己写配置文件。这是因为mybatis为springboot写好了默认配置文件,而springboot解析加载了默认配置。
  • 其原理是这样的,@SpringBootApplication包含了@EnableAutoConfiguration,这个注解又包含@Import(AutoConfigurationImportSelector.class)。springboot启动时,会调用AutoConfigurationImportSelector的回调函数,让其解析所有包下的自动配置文件(文件名是springboot规定的),并加载为Bean。这便是springboot能自动配置的原因。
  • AutoConfigurationImportSelector的回调函数在调用时,会解析以下几种文件:
    1. spring.factories
    2. spring-configuration-metadata.json和additional-spring-configuration-metadata.json
    3. spring-autoconfigure-metadata.properties

    所以,如果一个包希望能适配springboot的自动配置功能,就要填写上面的文件(视需要而定,填写其中的几个)。

1. 自动配置文件

1.1 什么是自动配置文件

在上文,我列举了好几个自动配置文件,当你需要开发的包被springboot自动配置时,就要填写上面的一个或更多配置文件。 这些文件的文件名是固定的,其内容也要遵循一定的格式。springboot只会固定搜索每个包下这些文件名对应的文件,并解析。 你可能会疑惑,这些自动文件分别有什么作用?怎么编写自己的自动配置文件呢?

1.2 自动配置文件的作用和编写

  • Appendix B. Configuration Metadata 提到了META-INF/spring-configuration-metadata.jsonMETA-INF/additional-spring-configuration-metadata.json
    • 前者一般会随IDE自动生成,在build期间,IDE会检查你的配置代码和注释,并生成。后者人为书写的,用来对前者进行补充。
    • 实际操作时,一般前者是自动生成的,如果需求有变,不用在这个文件上修改,只需修改源代码->maven clean->maven build重新生成一次前者即可。而后者一般是在IDE自动生成的基础上,对前者进行补充。
    • 前者和后者的书写格式是一致的,起到相同的作用。只不过我们一般让IDE maven build来生成前者,而后者由开发者另外编写,以便补充自动生成的前者。
    • spring会读取这两个文件,在内部合并配置。
  • 49. Creating Your Own Auto-configuration提到了META-INF/spring.factoriesMETA-INF/spring-autoconfigure-metadata.properties

参考

@EnableAutoConfiguration作用原理

先看springboot2.0自动注入文件spring.factories如何加载详解 AutoConfigurationImportSelector.java:

protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

调用了getCandidateConfigurations:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
    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;
}

然后又调用了

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

我们看到

loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());

这句话。loadSpringFactories(classLoader)的返回值是Map<String, List<String>>,它分析所有包下的META-INF/spring.factories,将其中配置的k-v对合并。 getOrDefault(factoryClassName, Collections.emptyList());中,factoryClassName的值是org.springframework.boot.autoconfigure.EnableAutoConfiguration(参考文章已经分析过为什么)。 所以合并起来,这句话的意思就是,读取所有包下的META-INF/spring.factories,将其中配置的k-v对合并,再读取key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的value数组,将这个数组返回。这也正是loadFactoryNames所做的事情了。 之后理解getAutoConfigurationEntry这个方法:

AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

大概就是configurations = removeDuplicates(configurations);去除重复的配置类,configurations.removeAll(exclusions);去除要除外的配置类,最后封装一下,返回结果。 我们再看到外层的AutoConfigurationImportSelector.selectImports:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
            autoConfigurationMetadata, annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

中最后一句话:

StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());

通过autoConfigurationEntry.getConfigurations()获取要配置的类,再转换成String数组,包含要导入的配置。之后由框架加载。

Springboot 对@Import注解的处理过程

阅读Springboot - @Import 详解 的 Springboot 对@Import注解的处理过程 和Spring 工具类 ConfigurationClassParser 分析得到配置类在ConfigurationClassParser.parse中处理配置类

springboot处理@Import的分析

  1. springboot初始化的普通context(非web) 是AnnotationConfigApplicationContext, 在初始化的时候会初始化两个工具类, AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 分别用来从 annotation driven 的配置和xml的配置中读取beanDefinition并向context注册。
  2. 在初始化 AnnotatedBeanDefinitionReader 的时候, 会向BeanFactory注册一个ConfigurationClassPostProcessor 用来处理所有的基于annotation的bean, 这个ConfigurationClassPostProcessor 是 BeanFactoryPostProcessor 的一个实现
  3. springboot启动时,在AbstractApplicationContext -> refresh() -> invokeBeanFactoryPostProcessors(beanFactory) 方法中调用注册到它上边的所有的BeanFactoryPostProcessor,其中就包括ConfigurationClassPostProcessor。
  4. 在上一步中,ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry回调函数会调用,然后postProcessBeanDefinitionRegistry->processConfigBeanDefinitions->parser.parse(candidates);让ConfigurationClassParser处理所有配置类
  5. parse函数根据Bean定义的不同类型,走不同的分支。但无论哪种情况,最终都会调用processConfigurationClass->doProcessConfigurationClass。然后在此处理各种配置上的注解。 另外,在处理完所有配置类后,调用processDeferredImportSelectors(),处理DeferredImportSelector的实现类

配博客原文的一张图帮助理解:

剩下的看引入的博客就好。

结论

  1. @EnableAutoConfiguration通过 @Import(AutoConfigurationImportSelector.class)来作用。
  2. 在框架加载时,会处理@Import注解(上文已经说了springboot怎么处理@Import的了。),调用AutoConfigurationImportSelector.selectImports方法,把每个包内的META-INF/spring.factories读取,并把org.springframework.boot.autoconfigure.EnableAutoConfiguration的自动配置类都读取。
  3. AutoConfigurationImportSelector.selectImports本身只是读取值,将要加载的自动配置类数组返回,而并不负责加载。返回该数组后,框架就会将其加载。

Mybatis的自动加载

了解了原理,我们看到mybatis-autoconfigure包下的spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

其中,MybatisAutoConfiguration是一个@Configuration,定义了一些默认的Bean。 所以,该文件通过让MybatisAutoConfiguration自动加载,引入了一些默认的Bean,比如SqlSessionFactory、SqlSessionTemplate等。

spring-boot-configuration-processor作用

spring-boot-configuration-processor的作用认为是用来引入@PropertySource的。 也有别的博客说是用来使@ConfigurationProperties生效的。 但我发现不导入spring-boot-configuration-processor也能使用这两个注解。所以我也搞不懂spring-boot-configuration-processor是做什么的。

@ConfigurationProperties与@PropertySource共同作用

SpringBoot标签之@ConfigurationProperties、@PropertySource注解的使用

  • 当获取主配置文件中属性值时,只需@ConfigurationProperties(prefix = "person")注解来修饰某类,其作用是告诉springBoot,此类中的属性将与默认的全局配置文件中对应属性一一绑定。属性名必须是application.yml或application.properties。【prefix = "person"】表示与配置文件中哪个层级的属性进行绑定。
  • 当一些属性不想配置到主配置文件,需自定义一个配置文件,需通过@PropertySource注解指定此配置文件路径。并用@ConfigurationProperties(prefix = "xxx")注解指定自定义配置文件中哪个层级属性需绑定。 @ConfigurationProperties(prefix = "person") @PropertySource(value ={"classpath:person.properties"}) @Validated public class Person {...}
  • 配置文件的位置:\src\main\resources\application.yml

spring-configuration-metadata.json

Pimp your config with configuration meta-data in Spring Boot讲得挺好,配上了许多截图和详细的讲述。

Once the build is over, you can explore the target folder (in case of using maven) and look for newly added file target\classes\META-INF\spring-configuration-metadata.json.

可见,spring-configuration-metadata.json会随着build操作自动生成在META-INF下。

  • Advanced config with configuration meta-data in Spring Bootstrap 上文的后续,进一步进行讲解配置。
  • 官方文档配合食用。 To support such cases and let you provide custom "hints", the annotation processor automatically merges items from META-INF/additional-spring-configuration-metadata.json into the main metadata file. 插件annotation processor会将additional-spring-configuration-metadata.json合并到主metadata文件中。 If you refer to a property that has been detected automatically, the description, default value, and deprecation information are overridden, if specified. If the manual property declaration is not identified in the current module, it is added as a new property. additional metadata文件所配置的属性,相比较主metadata文件,如果重复,则覆盖。如果不重复,则新添加。
  • SpringBoot教程三】:配置文件和多环境配置明确指出additional-spring-configuration-metadata.json是放在src/main/resources/META-INF下的,而不是target/classes/META-INF下。

ImportSelector DeferredImportSelector

ImportSelector DeferredImportSelector区别 DeferredImportSelector会在其它ImportSelector加载完成后才加载。

@ConditionalOnClass

@ConditionalOnClass的作用

@Configuration
@ConditionalOnClass({Billy.class})
public class VanConfig {
  // ...
}

作用在@Configuration上,当Billy.class存在于classpath时,才加载VanConfig。 conditional系列

spring-autoconfigure-metadata.properties是什么

深入理解SpringBoot的过滤条件--AutoConfigure SpringBoot中@EnableAutoConfiguration注解的作用

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励