@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