[关闭]
@zhuanxu 2018-02-28T11:25:30.000000Z 字数 7988 阅读 2472

Attack-spring-boot-2:spring-boot 配置解析

spring-boot


配置形式

spring-boot的配置包括:

外化配置通过将可变量分离出来,使得我们可以在不同的环境中使用同一份代码。

外化配置表现形式不单单是 .properties 和 .yml 属性文件,还可以使用环境变量和命令行参数等来实现。

由于有好多外部配置来源,所以需要解决配置冲突问题,解决方案是通过优先级,下面是属性读取顺序:

  1. 本地 Devtools 全局配置
  2. 测试时 @TestPropertySource 注解配置
  3. 测试时 @SpringBootTest 注解的 properties 配置
  4. 命令行配置
  5. SPRINGAPPLICATIONJSON 配置
  6. ServletConfig 初始化参数配置
  7. ServletContext 初始化参数配置
  8. Java 环境的 JNDI 参数配置
  9. Java 系统的属性配置
  10. OS 环境变量配置
  11. 只能随机属性的 RandomValuePropertySource 配置
  12. 工程 jar 之外的多环境配置文件(application- {profile}.properties 或 YAML)
  13. 工程 jar 之内的多环境配置文件(application- {profile}.properties 或 YAML)
  14. 工程 jar 之外的应用配置文件(application.properties 或 YAML)
  15. 工程 jar 之内的应用配置文件(application.properties 或 YAML)
  16. @Configuration 类中的 @PropertySource 注解配置
  17. 默认属性配置(SpringApplication.setDefaultProperties 指定)

配置使用方法

讲了配置后,下一步就是介绍使用方法了:

  1. 通过@Value注解
  2. 通过@ConfigurationProperties绑定到配置类上
  3. 通过Environment获取

先看第一个@Value注解

第二个@ConfigurationProperties注解
这个东西是个啥呢?看代码:

顺着代码注释我们去看ConfigurationPropertiesBindingPostProcessorEnableConfigurationProperties

ConfigurationPropertiesBindingPostProcessor

我们先来看ConfigurationPropertiesBindingPostProcessor

上面继承图中 ApplicationContextAware 是 Spring 提供的获取 Spring 上下文中指定对象的方法,会在 BeanPostProcessor 之前调用,我们先来看 BeanPostProcessor

BeanPostProcessor

BeanPostProcessor 接口定义了两个方法:

所以我们只需要实现BeanPostProcessor接口,就能够控制Bean初始化前后的操作。

下面我们来探究下到底是怎么实现的,查看实现的原理的万能方式是打断点调试,来看下我们调用栈:

我们先来看第一个方法:AbstractAutowireCapableBeanFactory.initializeBean

  1. initializeBean
  2. invokeAwareMethods:负责设置 Spring 上下文中指定对象
  3. applyBeanPostProcessorsBeforeInitialization
  4. invokeInitMethodsbean年初始化
  5. applyBeanPostProcessorsAfterInitialization

上面我们列举出了主要的方法,看名字大概就能知道功能,来看applyBeanPostProcessorsAfterInitialization


看里面的重点函数getBeanPostProcessors

  1. /** BeanPostProcessors to apply in createBean */
  2. private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();
  3. public List<BeanPostProcessor> getBeanPostProcessors() {
  4. return this.beanPostProcessors;
  5. }

接着我们看 BeanPostProcessor 对象都添加到集合中去的时机,我们会发现中文件中有个addBeanPostProcessor方法,还是同样方法,打断点看调用链,会发现有好多个地方都调用了
addBeanPostProcessor方法,我们一步一步调试,会发现registerBeanPostProcessors方法:

  1. refresh
  2. registerBeanPostProcessors
  3. PostProcessorRegistrationDelegate.registerBeanPostProcessors
  4. beanFactory.getBeanNamesForType
  5. ...

上面一步一步最后会调用到getBeanNamesForType来获取BeanPostProcessor类型的Bean,然后都通过addBeanPostProcessor方法添加。

到此我们分析完了BeanPostProcessorConfigurationPropertiesBindingPostProcessor还剩下InitializingBean,看上图

InitializingBean 中 afterPropertiesSet 方法处打断点。

可以说是看到了老朋友,也是AbstractAutowireCapableBeanFactory.initializeBean方法,里面的initializeBean方法如下:

  1. invokeInitMethods
  2. if bean instanceof InitializingBean:
  3. bean.afterPropertiesSet()

好了到目前,我们看完了ConfigurationPropertiesBindingPostProcessor的主要实现接口,根据之前的分析能得到接口方法的调用顺序为:

postProcessBeforeInitialization -> afterPropertiesSet -> postProcessAfterInitialization

ConfigurationPropertiesBindingPostProcessor中没有实现postProcessAfterInitialization,因次我们先看第一个方法。

  1. @Override
  2. public Object postProcessBeforeInitialization(Object bean, String beanName)
  3. throws BeansException {
  4. // 获取 ConfigurationProperties 注释
  5. ConfigurationProperties annotation = getAnnotation(bean, beanName,
  6. ConfigurationProperties.class);
  7. if (annotation != null) {
  8. // 进行数据绑定
  9. bind(bean, beanName, annotation);
  10. }
  11. return bean;
  12. }

我们重点看属性值注入bind方法。

  1. private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
  2. ResolvableType type = getBeanType(bean, beanName);
  3. Annotation[] annotations = new Annotation[] { annotation }
  4. Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);
  5. this.configurationPropertiesBinder.bind(target);
  6. }

其中 ConfigurationPropertiesBinder 负责数据的绑定,阅读过程中发现Springboot2.0中对绑定进行了重写,具体是包:org.springframework.boot.context.properties.bind,下次分析的。

EnableConfigurationProperties

还记得之前的图:

介绍完 ConfigurationPropertiesBindingPostProcessor 后,我们介绍 EnableConfigurationProperties。

  1. @Import(EnableConfigurationPropertiesImportSelector.class)
  2. public @interface EnableConfigurationProperties {
  3. Class<?>[] value() default {};
  4. }

@Import 可以将对应的 Bean 导入到 Spring 上下文中。如果类在工程中的话那么直接使用 @Configuration 注解即可,Spring 会自动识别的。但是如果在其他 jar 包或框架上,没有配置到自动扫描的目录中或者是没有添加 @Configuration 注解,那么就需要使用 @Import 将其导入到项目中来。

  1. public @interface Import {
  2. /**
  3. * 可以是 Configuration, ImportSelector, ImportBeanDefinitionRegistrar
  4. */
  5. Class<?>[] value();
  6. }

此处 EnableConfigurationPropertiesImportSelector 实现了 ImportSelector 的 selectImports 方法:

  1. public interface ImportSelector {
  2. /**
  3. * Select and return the names of which class(es) should be imported based on
  4. * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
  5. */
  6. String[] selectImports(AnnotationMetadata importingClassMetadata);
  7. }

其中 selectImports 根据注解信息来返回要import的类名,所以我们来看EnableConfigurationPropertiesImportSelector

里面用到了我们第二个有用的类ImportBeanDefinitionRegistrar:

  1. public interface ImportBeanDefinitionRegistrar {
  2. // 注册Bean
  3. public void registerBeanDefinitions(
  4. AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
  5. }

到这里我们来捋下整个思路:

  1. 我们的出发点是想看 为何加个注解 ConfigurationProperties 到类上,就能实现自动属性注入了
  2. 我们去看了注解 ConfigurationProperties 的定义,注释中让我们去看 ConfigurationPropertiesBindingPostProcessor 和 EnableConfigurationProperties
  3. 我们先去看了 ConfigurationPropertiesBindingPostProcessor ,给出了类的继承图,上面需要关注的是 BeanPostProcessor 和 InitializingBean
  4. BeanPostProcessor 和 InitializingBean 一起给加载 Bean 的过程中流出了3个扩展点,调用的顺序是:postProcessBeforeInitialization -> afterPropertiesSet -> postProcessAfterInitialization
  5. 在 postProcessBeforeInitialization 中完成了属性的绑定
  6. 接着我们要去看 EnableConfigurationProperties 注解,发现里面的@Import是加载需要的Bean进来

以上就是我们目前的一个解决问题的思路,下面我们来看 EnableConfigurationProperties 这个Enable**是在哪个地方引入到SpringBoot中的呢?

我们会发现在包org.springframework.boot.autoconfigure下的context.ConfigurationPropertiesAutoConfiguration

  1. @Configuration
  2. @EnableConfigurationProperties
  3. public class ConfigurationPropertiesAutoConfiguration {
  4. }

至于 ConfigurationPropertiesAutoConfiguration 这个加载进来呢?
这就到了 Spring-boot的精髓 EnableAutoConfiguration 。

EnableAutoConfiguration

我们可以在包org.springframework.boot.autoconfigure中的spring.factories中定义了好多自动配置的类:

所以我们就到了EnableAutoConfiguration

接着我们就会到了@SpringBootApplication,里面有注解 EnableAutoConfiguration

我们下一步自然就进入@Import(AutoConfigurationImportSelector.class),里面的 selectImports 将 Bean 加载进来:

其中 DeferredImportSelector 的作用是在所有Bean处理完后最后再处理,特别适合Bean是根据条件Import进来的。


挨行来看代码,

  1. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
  2. .loadMetadata(this.beanClassLoader);

读取了 META-INF/spring-autoconfigure-metadata.properties 配置文件,改配置文件中的内容如下:

配置文件中配置了 SpringBoot 自动集成的各种 Enable 框架的执行条件,比如定义与其他 AutoConfiguration 框架的执行顺序,需要哪些 Bean 在的时候才可以执行等。

  1. AnnotationAttributes attributes = getAttributes(annotationMetadata);

上面是读取 @EnableAutoConfiguration 注解中配置的 exclude,excludeName 两个属性值。

  1. List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);

此处读取 META-INF/spring.factories 配置文件中对应 key:org.springframework.boot.autoconfigure.EnableAutoConfiguration 所对应的类,具体是通过:

其中SpringFactoriesLoader.loadFactoryNames负责将 META-INF/spring.factories 中的key为EnableAutoConfiguration的值读入。
整个流程如下:

图片来自 第05课:@EnableAutoConfiguration 原理与实战

EnableConfigurationPropertiesImportSelector

到这我们将介绍我们最后一部分,我们了解了 EnableConfigurationProperties 是如何开启的了,下面就看其加载进入了哪些Bean进来。

我们先不看代码,先讲下这个逻辑,我们有@SpringBootApplication,其开启了@EnableAutoConfiguration,而 @EnableAutoConfiguration 会通过@Import去加载进 META-INF/spring-autoconfigure-metadata.properties 中定义的 autoconfigure 类来, 而其中就包含了 EnableConfigurationProperties,下面我们就看看 EnableConfigurationProperties 的 @Import 去将有相关 的Bean加载进来了,整个逻辑清晰后,我们就能来看 EnableConfigurationPropertiesImportSelector 了,具体方法还是打断点,里面会有两个方法来注册Bean。

会将 EnableConfigurationProperties 中写的Bean注册进来,一个例子就是:

上面会将JacksonProperties加载进来。

这里就将我们之前介绍的ConfigurationPropertiesBindingPostProcessor加载了进来。

总结

以上就是我们的整个spring-boot的配置原理解析,总结下整个思路:

  1. 我们的出发点是想看 为何加个注解 ConfigurationProperties 到类上,就能实现自动属性注入了
  2. 我们去看了注解 ConfigurationProperties 的定义,注释中让我们去看 ConfigurationPropertiesBindingPostProcessor 和 EnableConfigurationProperties
  3. 我们先去看了 ConfigurationPropertiesBindingPostProcessor ,给出了类的继承图,上面需要关注的是 BeanPostProcessor 和 InitializingBean
  4. BeanPostProcessor 和 InitializingBean 一起给加载 Bean 的过程中流出了3个扩展点,调用的顺序是:postProcessBeforeInitialization -> afterPropertiesSet -> postProcessAfterInitialization
  5. 在 postProcessBeforeInitialization 中完成了属性的绑定
  6. 接着我们要去看 EnableConfigurationProperties 注解,发现里面的@Import是加载需要的Bean进来
  7. EnableConfigurationProperties @Import 进来的Bean就包含了之前的 ConfigurationPropertiesBindingPostProcessor

你的鼓励是我继续写下去的动力,期待我们共同进步。
这个时代,每个人都是超级个体!关注我,一起成长!

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注