@zhuanxu
2018-02-28T19:25:30.000000Z
字数 7988
阅读 2664
spring-boot
spring-boot的配置包括:
外化配置通过将可变量分离出来,使得我们可以在不同的环境中使用同一份代码。
外化配置表现形式不单单是 .properties 和 .yml 属性文件,还可以使用环境变量和命令行参数等来实现。
由于有好多外部配置来源,所以需要解决配置冲突问题,解决方案是通过优先级,下面是属性读取顺序:
讲了配置后,下一步就是介绍使用方法了:
@Value
注解@ConfigurationProperties
绑定到配置类上Environment
获取 先看第一个@Value
注解
第二个@ConfigurationProperties
注解
这个东西是个啥呢?看代码:
顺着代码注释我们去看ConfigurationPropertiesBindingPostProcessor
和EnableConfigurationProperties
。
我们先来看ConfigurationPropertiesBindingPostProcessor
:
上面继承图中 ApplicationContextAware 是 Spring 提供的获取 Spring 上下文中指定对象的方法,会在 BeanPostProcessor 之前调用,我们先来看 BeanPostProcessor。
BeanPostProcessor 接口定义了两个方法:
所以我们只需要实现BeanPostProcessor
接口,就能够控制Bean初始化前后的操作。
下面我们来探究下到底是怎么实现的,查看实现的原理的万能方式是打断点调试,来看下我们调用栈:
我们先来看第一个方法:AbstractAutowireCapableBeanFactory.initializeBean
,
initializeBean
invokeAwareMethods:负责设置 Spring 上下文中指定对象
applyBeanPostProcessorsBeforeInitialization
invokeInitMethods:bean年初始化
applyBeanPostProcessorsAfterInitialization
上面我们列举出了主要的方法,看名字大概就能知道功能,来看applyBeanPostProcessorsAfterInitialization
。
看里面的重点函数getBeanPostProcessors
。
/** BeanPostProcessors to apply in createBean */
private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();
public List<BeanPostProcessor> getBeanPostProcessors() {
return this.beanPostProcessors;
}
接着我们看 BeanPostProcessor 对象都添加到集合中去的时机,我们会发现中文件中有个addBeanPostProcessor
方法,还是同样方法,打断点看调用链,会发现有好多个地方都调用了
addBeanPostProcessor
方法,我们一步一步调试,会发现registerBeanPostProcessors
方法:
refresh
registerBeanPostProcessors
PostProcessorRegistrationDelegate.registerBeanPostProcessors
beanFactory.getBeanNamesForType
...
上面一步一步最后会调用到getBeanNamesForType
来获取BeanPostProcessor
类型的Bean,然后都通过addBeanPostProcessor
方法添加。
到此我们分析完了BeanPostProcessor
,ConfigurationPropertiesBindingPostProcessor
还剩下InitializingBean
,看上图
InitializingBean
中 afterPropertiesSet 方法处打断点。
可以说是看到了老朋友,也是AbstractAutowireCapableBeanFactory.initializeBean
方法,里面的initializeBean
方法如下:
invokeInitMethods
if bean instanceof InitializingBean:
bean.afterPropertiesSet()
好了到目前,我们看完了ConfigurationPropertiesBindingPostProcessor
的主要实现接口,根据之前的分析能得到接口方法的调用顺序为:
postProcessBeforeInitialization -> afterPropertiesSet -> postProcessAfterInitialization
ConfigurationPropertiesBindingPostProcessor
中没有实现postProcessAfterInitialization
,因次我们先看第一个方法。
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// 获取 ConfigurationProperties 注释
ConfigurationProperties annotation = getAnnotation(bean, beanName,
ConfigurationProperties.class);
if (annotation != null) {
// 进行数据绑定
bind(bean, beanName, annotation);
}
return bean;
}
我们重点看属性值注入bind方法。
private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
ResolvableType type = getBeanType(bean, beanName);
Annotation[] annotations = new Annotation[] { annotation }
Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);
this.configurationPropertiesBinder.bind(target);
}
其中 ConfigurationPropertiesBinder
负责数据的绑定,阅读过程中发现Springboot2.0中对绑定进行了重写,具体是包:org.springframework.boot.context.properties.bind
,下次分析的。
还记得之前的图:
介绍完 ConfigurationPropertiesBindingPostProcessor 后,我们介绍 EnableConfigurationProperties。
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
Class<?>[] value() default {};
}
@Import 可以将对应的 Bean 导入到 Spring 上下文中。如果类在工程中的话那么直接使用 @Configuration 注解即可,Spring 会自动识别的。但是如果在其他 jar 包或框架上,没有配置到自动扫描的目录中或者是没有添加 @Configuration 注解,那么就需要使用 @Import 将其导入到项目中来。
public @interface Import {
/**
* 可以是 Configuration, ImportSelector, ImportBeanDefinitionRegistrar
*/
Class<?>[] value();
}
此处 EnableConfigurationPropertiesImportSelector 实现了 ImportSelector 的 selectImports 方法:
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
其中 selectImports 根据注解信息来返回要import的类名,所以我们来看EnableConfigurationPropertiesImportSelector
,
里面用到了我们第二个有用的类ImportBeanDefinitionRegistrar
:
public interface ImportBeanDefinitionRegistrar {
// 注册Bean
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
到这里我们来捋下整个思路:
以上就是我们目前的一个解决问题的思路,下面我们来看 EnableConfigurationProperties 这个Enable**是在哪个地方引入到SpringBoot中的呢?
我们会发现在包org.springframework.boot.autoconfigure
下的context.ConfigurationPropertiesAutoConfiguration
:
@Configuration
@EnableConfigurationProperties
public class ConfigurationPropertiesAutoConfiguration {
}
至于 ConfigurationPropertiesAutoConfiguration 这个加载进来呢?
这就到了 Spring-boot的精髓 EnableAutoConfiguration 。
我们可以在包org.springframework.boot.autoconfigure
中的spring.factories
中定义了好多自动配置的类:
所以我们就到了EnableAutoConfiguration
:
接着我们就会到了@SpringBootApplication,里面有注解 EnableAutoConfiguration
我们下一步自然就进入@Import(AutoConfigurationImportSelector.class),里面的 selectImports 将 Bean 加载进来:
其中 DeferredImportSelector 的作用是在所有Bean处理完后最后再处理,特别适合Bean是根据条件Import进来的。
挨行来看代码,
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
读取了 META-INF/spring-autoconfigure-metadata.properties 配置文件,改配置文件中的内容如下:
配置文件中配置了 SpringBoot 自动集成的各种 Enable 框架的执行条件,比如定义与其他 AutoConfiguration 框架的执行顺序,需要哪些 Bean 在的时候才可以执行等。
AnnotationAttributes attributes = getAttributes(annotationMetadata);
上面是读取 @EnableAutoConfiguration 注解中配置的 exclude,excludeName 两个属性值。
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 原理与实战
到这我们将介绍我们最后一部分,我们了解了 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的配置原理解析,总结下整个思路:
你的鼓励是我继续写下去的动力,期待我们共同进步。