spring 自动配置(中) 自动配置原理

时间:2022-06-22
本文章向大家介绍spring 自动配置(中) 自动配置原理,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

@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。然后在此处理各种配置上的注解。 另外,parse方法内,在处理完所有配置类后,调用this.deferredImportSelectorHandler.process();,处理DeferredImportSelector的实现类
  6. doProcessConfigurationClass->processImports中,对于普通ImportSelector会调用selectImports,对于DeferredImportSelector会先加入List中,在this.deferredImportSelectorHandler.process();中回调其process方法。

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

另外,由于spring版本问题,图中的processDeferredImportSelector应改为this.deferredImportSelectorHandler.process();。 剩下的看引入的博客就好。

结论

  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 {...}
  • 配置文件的位置:srcmainresourcesapplication.yml

META-INF/spring-autoconfigure-metadata.properties

这个文件是如何被加载的:

  1. AutoConfigurationImportSelector是一个DeferredImportSelector,在spring加载BeanFactoryPostProcessor->加载@Imports时,调用回调函数DeferredImportSelector.process。
  2. 在process内,有代码如下: AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
    1. 首先调用getAutoConfigurationMetadata()。该方法解析所有的META-INF/spring-autoconfigure-metadata.properties,以key-value对的形式存储在AutoConfigurationMetadata中。
    2. 然后调用getAutoConfigurationEntry,传入了上一步的参数。该方法调用的configurations = filter(configurations, autoConfigurationMetadata);用到了上一步传入的参数
  3. filter方法。调用了filter.match(candidates, autoConfigurationMetadata);,看到match的接口注释,就知道该方法返回一个bool数组,代表candidates中哪些是Configuration是满足加载条件的。 至于match又是怎么做的,再往深我就没探究了。

这个文件毕竟是spring-boot-autoconfigure-processor自动生成的,用于spring加快加载速度用的,我们只要会用就好,不必过于关注其原理。

ImportSelector DeferredImportSelector

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

// ConfigurationClassParser.processImports
if (selector instanceof DeferredImportSelector) {
    this.deferredImportSelectorHandler.handle(
            configClass, (DeferredImportSelector) selector);
}

DeferredImportSelector的回调: ConfigurationClassParser.parse->this.deferredImportSelectorHandler.process();->handler.processGroupImports();。 其中,

  • grouping.getImports()调用了回调函数 process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector)selectImports()
  • grouping.getImports().forEach内调用了processImports加载
  • 而selectImports(AnnotationMetadata annotationMetadata)并没有被调用。

@ConditionalOnClass

@ConditionalOnClass的作用

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

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