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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM