![cover](https://img-blog.csdnimg.cn/img_convert/1f20816e15694cf3b56262c1f87e2a14.png)
springboot之@ConfigurationProperties分析
写在前面关于springboot系列详细分析,可以参考这里。
![](https://csdnimg.cn/release/devpress/public/img/ic-book.4f347164.png)
写在前面
关于springboot系列详细分析,可以参考这里。
1:使用
在使用springboot的时候,我们可能会有如下的需求,希望创建一个对象,但是呢,对象的属性是动态的,希望可配置,此时我们可能有如下的选择:
- 1:自己读取配置文件,获取配置的属性,动态赋值
- 2:使用@PropertySource注解和@Value注解组合
- 3:使用@ConfigurationProperties注解
其中1
比较麻烦,显然不可取,2
如果是在纯spring的环境中,可以采取该方案,具体可以参考这篇文章,3
是在springboot环境中提供的方案,也是本文我们要分析的重点。其中,一般我们有两种方式来使用ConfigurationProperties,第一种是使用@Component+@ConfigurationProperties
,第二种是使用@EnableConfigurationProperties+@ConfigurationProperties
,分别来看下。
1.1:@Component+@ConfigurationProperties
1.1.1:定义bean
@ConfigurationProperties(prefix = "mypreffix1")
@Component
public class MyComponentConfigurationProperties {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "MyConfigurationProperties{" +
"name='" + name + '\'' +
'}';
}
}
默认从application.properties/yml
文件中读取,如果是需要从其它配置文件中读取的话,可以通过@PropertySource
配置,可能如下:
@Component("myBean")
@PropertySource("classpath:myPropertySourceBean.properties")
@ConfigurationProperties(prefix = "mybean")
public class MyPropertySourceBean {}
1.1.2:定义配置
application.properties:
mypreffix1.name="the name by ConfigurationProperties1s"
1.1.3:测试代码
@SpringBootApplication(
scanBasePackages = { "dongshi.daddy.springboothelloworld",
"dongshi.daddy.configurationproperties" }
)
public class SpringbootHelloWorldApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(SpringbootHelloWorldApplication.class);
ConfigurableApplicationContext ac = springApplication.run(args);
MyComponentConfigurationProperties myComponentConfigurationProperties = ac.getBean(MyComponentConfigurationProperties.class);
System.out.println(myComponentConfigurationProperties);
}
}
运行:
2021-06-26 10:08:14.199 INFO 5258 --- [ main] d.d.s.SpringbootHelloWorldApplication : Started SpringbootHelloWorldApplication in 2.515 seconds (JVM running for 3.313)
MyConfigurationProperties{name='"the name by ConfigurationProperties1s"'}
1.2:@EnableConfigurationProperties+@ConfigurationProperties
1.2.1:定义bean
@ConfigurationProperties(prefix = "mypreffix")
public class MyConfigurationProperties {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "MyConfigurationProperties{" +
"name='" + name + '\'' +
'}';
}
}
1.2.2:EnableAutoConfigurationProperties
@Configuration("myConfigurationPropertiesBean")
@EnableConfigurationProperties(MyConfigurationProperties.class)
public class MyConfigurationPropertiesBean {
@Resource
private MyConfigurationProperties myConfigurationProperties;
public MyConfigurationProperties getMyConfigurationProperties() {
return myConfigurationProperties;
}
public void setMyConfigurationProperties(MyConfigurationProperties myConfigurationProperties) {
this.myConfigurationProperties = myConfigurationProperties;
}
}
1.2.3:测试代码
@SpringBootApplication(
scanBasePackages = { "dongshi.daddy.springboothelloworld",
"dongshi.daddy.configurationproperties" }
)
public class SpringbootHelloWorldApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(SpringbootHelloWorldApplication.class);
ConfigurableApplicationContext ac = springApplication.run(args);
MyConfigurationPropertiesBean myConfigurationPropertiesBean = ac.getBean(MyConfigurationPropertiesBean.class);
System.out.println(myConfigurationPropertiesBean);
System.out.println(myConfigurationPropertiesBean.getMyConfigurationProperties());
}
}
运行:
2021-06-26 11:30:20.439 INFO 8100 --- [ main] d.d.s.SpringbootHelloWorldApplication : Started SpringbootHelloWorldApplication in 3.128 seconds (JVM running for 4.085)
dongshi.daddy.configurationproperties.MyConfigurationPropertiesBean$$EnhancerBySpringCGLIB$$de5fbbbe@606fc505
MyConfigurationProperties{name='"the name by ConfigurationProperties"'}
其中我们用到的EnableConfigurationProperies源码如下:
// 启用通过注解ConfigurationProperties注解配置的bean,我们使用常规的方式
// (比如@Bean,@Component等)来注册,或者是为了方便,通过该注解的value
// 来配置作为spring bean
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
// 设置使用了ConfigurationProperties注解,并需要注册为spring bean
// 的类的类型数组,最终的效果是,设置的类会从配置文件中读取相关信息
// 完成spring bean的创建
Class<?>[] value() default {};
}
2:执行过程分析
当我们执行run方法,首先执行到如下代码:
org.springframework.boot.SpringApplication#run(java.lang.String...)
// 1:启动应用程序 2:创建并返回spring容器
public ConfigurableApplicationContext run(String... args) {
...snip...
try {
...snip...
// 刷新容器
refreshContext(context);
...snip...
}
catch (Throwable ex) {
...snip...
}
...snip...
return context;
}
refreshContext执行关键源码如下:
org.springframework.boot.SpringApplication#refreshContext
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
...snip...
}
继续:
org.springframework.boot.SpringApplication#refresh
protected void refresh(ApplicationContext applicationContext) {
...snip...
((AbstractApplicationContext) applicationContext).refresh();
}
继续:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh
// 注意该方法还是springboot中的方法,重写了
// 加入了刷新异常后停止web服务器的逻辑
public final void refresh() throws BeansException, IllegalStateException {
try {
// <202106261716>
super.refresh();
}
catch (RuntimeException ex) {
// 异常,停止web服务器
stopAndReleaseWebServer();
throw ex;
}
}
继续看<202106261716>
处源码,这就是调用spring原有的容器刷新逻辑了,源码如下:
org.springframework.context.support.AbstractApplicationContext#refresh
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
...snip...
try {
...snip...
// <202106261722>
// 执行BeanFactoryPostProcessor,在生成了BeanDefinition
// 之后,在通过BeanDefinition生成bean之前调用,详细可以参考:https://blog.csdn.net/wang0907/article/details/115440135
invokeBeanFactoryPostProcessors(beanFactory);
...snip...
}
catch (BeansException ex) {
...snip...
}
...snip...
}
}
<202106261722>
处执行后会一直调用到处理@Configuration
注解的方法:
org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
...snip...
// 递归处理configuration的类,以及子类
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
} while (sourceClass != null);
...snip...
}
最终会执行到方法org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector#selectImports
,该方法源码如下:
org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector#selectImports
public String[] selectImports(AnnotationMetadata metadata) {
// <202106262010>
return IMPORTS;
}
注意到该方法正式在EnableConfigurationProperties注解中的Import注解中配置的EnableConfigurationPropertiesImportSelector
。<202106262010>
的IMPORTS定义如下:
org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector#IMPORTS
private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(),
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
这两个类是ConfigurationPropertiesBeanRegistrar
,ConfigurationPropertiesBindingPostProcessorRegistrar,其中
ConfigurationPropertiesBeanRegistrar参考
3:ConfigurationPropertiesBeanRegistrar,
ConfigurationPropertiesBindingPostProcessorRegistrar参考
4:ConfigurationPropertiesBindingPostProcessorRegistrar`。
3:ConfigurationPropertiesBeanRegistrar
该类的作用是引入在EnableConfigurationProperties注解中使用value配置的类为BeanDefinition对象。
源码如下:
org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector.ConfigurationPropertiesBeanRegistrar#registerBeanDefinitions
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// <202106262023>
getTypes(metadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type));
}
<202106262023>
处getTypes(metadata),metadata是封装类的注解信息的对象,比如都使用了哪些注解,注解中都定义了哪些属性等信息,源码如下:
org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector.ConfigurationPropertiesBeanRegistrar#getTypes
private List<Class<?>> getTypes(AnnotationMetadata metadata) {
// 获取EnableConfigurationProperties注解配置的信息的结果map
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);
// 获取所有属性配置中的value的信息集合,如下配置:
/*
@Configuration("myConfigurationPropertiesBean")
@EnableConfigurationProperties(MyConfigurationProperties.class)
public class MyConfigurationPropertiesBean {}
*/
// 则结果就是"class dongshi.daddy.configurationproperties.MyConfigurationProperties"
return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList());
}
获取了EnableConfigurationProperties注解配置的数组后,就可以通过<202106262023>
处register方法来注册对应的beandefinition了,具体参考3.1:register
。
3.1:register
源码如下:
org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector.ConfigurationPropertiesBeanRegistrar#register
private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory,
Class<?> type) {
// <202106262048>
String name = getName(type);
// 如果在bean工厂中不包含bean定义
if (!containsBeanDefinition(beanFactory, name)) {
// <202106262102>
registerBeanDefinition(registry, name, type);
}
}
<202106262048>
处是通过class类型生成bean的名称,源码如下:
org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector.ConfigurationPropertiesBeanRegistrar#getName
private String getName(Class<?> type) {
// 获取类上的ConfigurationProperties注解
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class);
// 获取注解的prefix属性,如"@ConfigurationProperties(prefix = "mypreffix")",
// 这此处的结果就是"mypreffix"
String prefix = (annotation != null) ? annotation.prefix() : "";
// 如果有前缀则使用"前缀-类型名称"的格式,否则直接适用类型名称,如:
/*
@ConfigurationProperties(prefix = "mypreffix")
public class MyConfigurationProperties {}
*/
// 我本地结果就是"mypreffix-dongshi.daddy.configurationproperties.MyConfigurationProperties"
return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
}
<202106262102>
处是注册beandefinition,源码如下:
org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector.ConfigurationPropertiesBeanRegistrar#registerBeanDefinition
private void registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class<?> type) {
// 要有ConfigurationProperties注解
assertHasAnnotation(type);
// 定义GenericBeanDefinition对象
GenericBeanDefinition definition = new GenericBeanDefinition();
// 设置class类型
definition.setBeanClass(type);
// 注册,即存储到"org.springframework.beans.factory.support.DefaultListableBeanFactory#beanDefinitionMap"中
registry.registerBeanDefinition(name, definition);
}
注意:到此处,只是注册了类型信息,还没有获取到在配置文件中配置的属性值信息!!!
4:ConfigurationPropertiesBindingPostProcessorRegistrar
源码如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar#registerBeanDefinitions
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// <202106270830>
if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
// <202106270856>
// 注册ConfigurationPropertiesBindingPostProcessor
registerConfigurationPropertiesBindingPostProcessor(registry);
// <202106270905>
// 注册ConfigurationBeanFactoryMetadata
registerConfigurationBeanFactoryMetadata(registry);
}
}
<202106270830>
处判断是否包含org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#BEAN_NAME
,这是bean处理器ConfigurationPropertiesBindingPostProcessor在容器中bean名称,关于ConfigurationPropertiesBindingPostProcessor参考4.1:ConfigurationPropertiesBindingPostProcessor
。<202106270856>
处源码如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar#registerConfigurationPropertiesBindingPostProcessor
private void registerConfigurationPropertiesBindingPostProcessor(BeanDefinitionRegistry registry) {
// 创建GenericBeanDefinition对象实例,并设置相关信息
GenericBeanDefinition definition = new GenericBeanDefinition(); definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 注册到bean定义注册器中
registry.registerBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition);
}
<202106270905>
是注册ConfigurationBeanFactoryMetadata,源码如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar#registerConfigurationBeanFactoryMetadata
private void registerConfigurationBeanFactoryMetadata(BeanDefinitionRegistry registry) {
// 创建GenericBeanDefinition对象实例,并设置相关属性
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(ConfigurationBeanFactoryMetadata.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 注册到bean定义注册器中
registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME, definition);
}
关于ConfigurationBeanFactoryMetadata具体参考4.2:ConfigurationBeanFactoryMetadata
。
4.1:ConfigurationPropertiesBindingPostProcessor
该类定义如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
// 绑定PropertySource的信息到使用了ConfigurationProperties注解的bean上
public class ConfigurationPropertiesBindingPostProcessor
implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {}
可以看到其实现了BeanPostProcessor接口会在bean初始化前调用方法postProcessBeforeInitialization
,bean初始化后方法postProcessAfterInitialization并没有实现(因为在接口中定义是default的,所以可以不用实现),关于postProcessBeforeInitialization方法具体参考4.1.1:postProcessBeforeInitialization
。
还实现了aware接口ApplicationContextAware,因此会通过方法setApplicationContext
设置应用程序上下文对象。
最后还实现了InitializingBean
,因此会在bean的属性设置完毕后调用方法afterPropertiesSet
,关于该方法参考4.1.2:afterPropertiesSet
。
4.1.1:postProcessBeforeInitialization
源码如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization
// bean:只进行了实例化,还初始化的bean对象
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// <202106270938>
// 获取ConfigurationProperties注解
ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class);
if (annotation != null) {
// <202106270942>
bind(bean, beanName, annotation);
}
return bean;
}
<202106270938>
处源码如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#getAnnotation
private <A extends Annotation> A getAnnotation(Object bean, String beanName, Class<A> type) {
// 获得bean上的注解
A annotation = this.beanFactoryMetadata.findFactoryAnnotation(beanName, type);
// 从bean上没有获取到注册,则从class上获取
if (annotation == null) {
annotation = AnnotationUtils.findAnnotation(bean.getClass(), type);
}
return annotation;
}
<202106270942>
处源码如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#bind
private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
// 获取bean的类型,源码如下:
/*
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#getBeanType
private ResolvableType getBeanType(Object bean, String beanName) {
// 我测试获取的为null,不直达何时不为null,先忽略
Method factoryMethod = this.beanFactoryMetadata.findFactoryMethod(beanName);
if (factoryMethod != null) {
return ResolvableType.forMethodReturnType(factoryMethod);
}
// 通过class获取类型
return ResolvableType.forClass(bean.getClass());
}
*/
ResolvableTypetype = getBeanType(bean, beanName);
// 获取Validated注解,认为没有即可
Validated validated = getAnnotation(bean, beanName, Validated.class);
// 默认validated注解为null,所以这里的结果是:[ConfigurationProperties]
Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
: new Annotation[] { annotation };
// 创建Bindable对象,内部封装了bean,annotations信息
Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);
try {
// <202106271027>
this.configurationPropertiesBinder.bind(target);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex);
}
}
<202106271027>
处是绑定属性信息到bean中,具体参考4.3:绑定属性信息到bean
4.1.2:afterPropertiesSet
源码如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#afterPropertiesSet
public void afterPropertiesSet() throws Exception {
this.beanFactoryMetadata = this.applicationContext.getBean(ConfigurationBeanFactoryMetadata.BEAN_NAME,
ConfigurationBeanFactoryMetadata.class);
this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(this.applicationContext,
VALIDATOR_BEAN_NAME);
}
4.2:ConfigurationBeanFactoryMetadata
该类主要处理通过方法+@Bean
方式组合创建bean情况的元信息。
4.2.1:postProcessBeanFactory
源码如下:
org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata#postProcessBeanFactory
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
// 遍历bean工厂中所有bean名称
for (String name : beanFactory.getBeanDefinitionNames()) {
// 获取当前bean定义的名称
BeanDefinition definition = beanFactory.getBeanDefinition(name);
// method是使用了@Bean注解的方法的名字
// bean是@Configuration所在的类的bean名字
/*
package foo.bar;
@Configuration
public class XXX {
@Bean
public Object m1() {
return new Object();
}
}
*/
// method的值就是"m1",bean的值就是"foo.bar.XXX"
String method = definition.getFactoryMethodName();
String bean = definition.getFactoryBeanName();
// 如果是method和bean都有值,则存储到bean工厂元信息对象beansFactoryMetadata中
if (method != null && bean != null) {
this.beansFactoryMetadata.put(name, new FactoryMetadata(bean, method));
}
}
}
4.3:绑定属性信息到bean
执行的方法是org.springframework.boot.context.properties.ConfigurationPropertiesBinder#bind
,源码如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBinder#bind
public void bind(Bindable<?> target) {
// 获取ConfigurationProperties注解
ConfigurationProperties annotation = target.getAnnotation(ConfigurationProperties.class);
// 断言,必须有ConfigurationProperties注解
Assert.state(annotation != null, () -> "Missing @ConfigurationProperties on " + target);
// <202106271105>
// 获取Validator集合
List<Validator> validators = getValidators(target);
// <202106271158>
BindHandler bindHandler = getBindHandler(annotation, validators);
// <202106271207>
getBinder().bind(annotation.prefix(), target, bindHandler);
}
<202106271105>
处是获取Validator集合,源码如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBinder#getValidators
private List<Validator> getValidators(Bindable<?> target) {
// 最多3个验证器,所以这里长度设置为3
List<Validator> validators = new ArrayList<>(3);
// 第1个验证器的来源,configurationPropertiesValidator
if (this.configurationPropertiesValidator != null) {
validators.add(this.configurationPropertiesValidator);
}
// 第2个验证器的来源,jsr303Present
if (this.jsr303Present && target.getAnnotation(Validated.class) != null) {
validators.add(getJsr303Validator());
}
// 第3个验证器,本身实现了Validator接口的情况
if (target.getValue() != null && target.getValue().get() instanceof Validator) {
validators.add((Validator) target.getValue().get());
}
// 返回验证器集合
return validators;
}
<202106271158>
处是获取BinderHandler,源码如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBinder#getBindHandler
private BindHandler getBindHandler(ConfigurationProperties annotation, List<Validator> validators) {
BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();
// 如果有ignoreInvalidFields,则进一步包装
if (annotation.ignoreInvalidFields()) {
handler = new IgnoreErrorsBindHandler(handler);
}
// 如果有ignoreUnknownFields,则进一步包装
if (!annotation.ignoreUnknownFields()) {
UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
handler = new NoUnboundElementsBindHandler(handler, filter);
}
// 如果有验证器,则进一步包装
if (!validators.isEmpty()) {
handler = new ValidationBindHandler(handler, validators.toArray(new Validator[0]));
}
// 忽略
for (ConfigurationPropertiesBindHandlerAdvisor advisor : getBindHandlerAdvisors()) {
handler = advisor.apply(handler);
}
// 返回,可以认为结果就是IgnoreTopLevelConverterNotFoundBindHandler
return handler;
}
<202106271207>
出getBinder方法源码如下:
org.springframework.boot.context.properties.ConfigurationPropertiesBinder#getBinder
private Binder getBinder() {
if (this.binder == null) {
// getConfigurationPropertySources():获取封装属性信息的对象
// getPropertySourcesPlaceholdersResolver():获取处理占位符的对象
// getPropertyEditorInitializer():获取用于编辑对象属性的对象
this.binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(),
getConversionService(), getPropertyEditorInitializer());
}
return this.binder;
}
<202106271207>
处的bind方法就是完成绑定配置信息到bean的属性中的工作了,具体是如何绑定的,属于org.springframework.boot.context.properties.bind
包中的内容,没有了解,知道即可先。
更多推荐
所有评论(0)