Spring 配置项解析: @Value vs @ConfigurationProperties

@ConfigurationProperties vs @Value

 

 

 

 https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/htmlsingle/#boot-features-external-config-vs-value

@Value 与 @ConfigurationProperties 的属性赋值不是同一个体系。
@Value 是 populateBean() 时进行属性注入的; 
@ConfigurationProperties 是 initializeBean() 时进行值绑定的(bind)。

 

@Value

@Value 是 populateBean() 时被当作依赖进行属性注入的;
@Value 与 @Ressource、@Autowired 的地位是相同的,都是使用在 field 上,做属性注入的。所以 Spring 把它们归在一起,都在 populateBean() 时进行处理。

具体的调用链如下:

1. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean()
    1.1 org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency()
        1.1.1 org.springframework.beans.factory.support.AbstractBeanFactory#resolveEmbeddedValue()  // 对 ${} 的处理
        1.1.2 org.springframework.beans.factory.support.AbstractBeanFactory#evaluateBeanDefinitionString() // 对 #{} 的处理

 

注:
@Value 使用在 field 字段上,最终会通过反射直接对字段进行设值,所以就算对应注入的 field 没有 setter 方法,也是可以进行注入的。
同时,还要注意,这种方式的注入,在 field 的 setter 或者 field 字段上打断点的话,是不会生效的,因为是使用反射直接设置的值。

 

@ConfigurationProperties

@ConfigurationProperties 是 initializeBean() 时进行绑定的。
具体是通过 ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization() 来处理的
解析值:
  org.springframework.boot.context.properties.bind.Binder 持有一个 org.springframework.boot.context.properties.bind.PlaceholdersResolver,用它来取 PropertySource 的值来进行解析
设置值:
  通过 Binder 来进行值绑定,最终会调用 field 字段的 setter 方法进行设置值

 (org.springframework.boot.context.properties.bind.Binder  一个容器对象,用来绑定值到对象上。)

 

 

补充:

Binder 的使用:

参看:org.springframework.boot.context.properties.ConfigurationPropertiesBinder#bind()

1. 创建 binder

this.binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(),
                    getConversionService(), getPropertyEditorInitializer(), null,
                    ConfigurationPropertiesBindConstructorProvider.INSTANCE);

// 构造方法参数说明:                    
org.springframework.boot.context.properties.bind.Binder#Binder(
java.lang.Iterable<org.springframework.boot.context.properties.source.ConfigurationPropertySource>,  // 用于属性绑定的配置源
org.springframework.boot.context.properties.bind.PlaceholdersResolver,  // 占位符解析器
org.springframework.core.convert.ConversionService,  // 类型转换服务接口
java.util.function.Consumer<org.springframework.beans.PropertyEditorRegistry>,  // 属性编辑器注册接口,与 ConversionService 一起服务于属性绑定期间所需的属性转换
org.springframework.boot.context.properties.bind.BindHandler, // 回调接口,可用于在元素绑定期间处理其他逻辑。如:绑定前的处理,绑定成功后的处理,绑定失败后的处理等
org.springframework.boot.context.properties.bind.BindConstructorProvider) // 用于确定绑定时要使用的特定构造函数的策略接口

 

org.springframework.core.convert.ConversionService  // 用于类型转换的服务接口。这是转换系统的入口点。通过调用convert(Object,Class)执行线程安全的类型转换。
org.springframework.core.convert.converter.GenericConverter#convert() // 最终实现类型转换的转换器,如:StringToDataSizeConverter 就可以将字符串 "12KB" 转成 DataSize 类型

org.springframework.beans.PropertyEditorRegistry // 用于注册JavaBeans PropertyEditor。它是 PropertyEditorRegistrar 操作的核心接口。由 BeanWrapper,DataBinder 扩展(主要处理 web 请求参数绑定)
org.springframework.validation.DataBinder // 将目标对象上的属性设值的绑定器,包括对验证和绑定结果分析的支持。

java.beans.PropertyEditor // 属性编辑器,可以将属性内容进行转换,比如:着色,String 转 boolean 等
org.springframework.beans.propertyeditors.CustomBooleanEditor // 将字符类型的 "true","false","on","off","yes","on" 转成 boolean 类型;将 boolean 类型转成字符类型。

 

2. 使用 binder 解析值

binder.bind(annotation.prefix(), target, bindHandler);

 

bean 创建的三步走:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
createBean() --> doCreateBean()

1. createBeanInstance()  // 通过反射创建 bean 的实例
2. populateBean()    // 填充 bean 里面的属性值,包括 @AutoWired、@Resource、@Value 标记的属性
3. initializeBean()    // 对 bean 进行初始化工作,包括一些初始化方法的执行,如:awareMethods、BeanPostProcessor、initMethods
// 参考方法:AbstractAutowireCapableBeanFactory#initializeBean()
// 其中,@ConfigurationProperties 标记的 bean 的属性注入,就选择了使用 BeanPostProcessor 来处理
// 具体的处理可查看 ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization()

 

StringValueResolver、EmbeddedValueResolver
EmbeddedValueResolver implements StringValueResolver。EmbeddedValueResolver 非常强大,它是 spring 配置解析的核心。先解析占位符 ${},再解析 spEL(#{})

 1 public class EmbeddedValueResolver implements StringValueResolver {
 2     private final BeanExpressionContext exprContext;
 3     private final BeanExpressionResolver exprResolver;
 4 
 5     public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
 6         this.exprContext = new BeanExpressionContext(beanFactory, null);
 7         this.exprResolver = beanFactory.getBeanExpressionResolver(); // 实现类为:StandardBeanExpressionResolver
 8     }
 9 
10     @Override
11     public String resolveStringValue(String strVal) {
12         // 1. 解析占位符${}
13         String value = this.exprContext.getBeanFactory().resolveEmbeddedValue(strVal);
14         if (this.exprResolver != null && value != null) {
15             // 2. 解析 spEL: #{}
16             Object evaluated = this.exprResolver.evaluate(value, this.exprContext);
17             value = (evaluated != null ? evaluated.toString() : null);
18         }
19         return value;
20     }
21 
22 }

 

配置写法小技巧:
可以使用嵌套写法 和 默认值。举例:
${server.error.path:${error.path:/error}}

 

为什么 yaml 文件中的配置支持 relaxed binding(中划线、下划线、大小写)
org.springframework.boot.context.properties.source.ConfigurationPropertyName 重写了 equals,支持 relaxed binding
将我们使用的配置 key 转换成配置源中的配置,然后再去配置源中获取配置。
比如:我们是 application.yml 中定义的是 con-fig.p1=aaa,而在使用时写的是 @ConfigurationProperties(prefix="config"),它会首先将 config.p1 转成 con-fig.p1,然后再获取配置

 

问题思考:
用了 disconf 之后,在使用 SpringBoot @ConfigurationProperties 形式的配置时,是不是就不用写 @PropertySource("classpath:redis.properties") 来指定配置文件了?因为 diconf 已经将托管的 property 配置文件纳入 spring 管理了,如果这样该多方便啊。那确实可以这么干吗?
(在使用 disconf 托管配置文件时,会配置 org.springframework.context.support.PropertySourcesPlaceholderConfigurer 或者 com.baidu.disconf.client.addons.properties.ReloadingPropertyPlaceholderConfigurer,被托管的配置都会纳入 spring 管理。)

分析:
通过测试发现是不可以的,因为 @ConfigurationProperties 最终委托给 Environment 来处理,而 PropertySourcesPlaceholderConfigurer 和 ReloadingPropertyPlaceholderConfigurer 都不受 Environment 控制,所以是不支持的。
而 @Value 却是可以注入 disconf 托管的配置的,因为 @Value 注入的配置会通过 StringValueResoulver 来解析,而 PropertySourcesPlaceholderConfigurer 和 ReloadingPropertyPlaceholderConfigurer 管理的配置,最终会间接通过 StringValueResolver 来解析

 

资料:
https://blog.csdn.net/f641385712/article/details/91380598

posted on 2020-01-19 14:46  快鸟  阅读(1071)  评论(0编辑  收藏  举报