Spring 配置项解析: @Value vs @ConfigurationProperties
@ConfigurationProperties 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