有時我們會使用@Value自動注入,同時也存在注入到集合、數組等復雜類型的場景。這都是方便寫 bug 的場景。
1 @Value未注入預期值
在字段或方法/構造函數參數級別使用,指示帶注釋元素的默認值表達式。
通常用于表達式驅動或屬性驅動的依賴注入。 還支持處理程序方法參數的動態解析
例如,在 Spring MVC 中,一個常見的用例是使用#{systemProperties.myProp} systemProperties.myProp #{systemProperties.myProp}樣式的 SpEL(Spring 表達式語言)表達式注入值。
或可使用${my.app.myProp}樣式屬性占位符注入值。
@Value實際處理由BeanPostProcessor執行,這意味著不能在BeanPostProcessor或BeanFactoryPostProcessor類型中使用 @Value。
V.S Autowired
在裝配對象成員屬性時,常使用@Autowired來裝配。但也使用@Value進行裝配:
- 使用@Autowired一般都不會設置屬性值
- @Value必須指定一個字符串值,因其定義做了要求:
一般都會因 @Value 常用于String類型的裝配,誤以為其不能用于非內置對象的裝配。
可用如下方式注入一個屬性成員:
使用 @Value更多是用來裝配String,而且支持多種強大的裝配方式
application.properties配置了這樣一個屬性:
user=admin password=pass
然后我們在一個Bean中,分別定義兩個屬性來引用它們:
password返回了配置值,但user卻不是配置文件的指定值,而是PC用戶名。
答疑
有一個正確的,說明 @Value使用姿勢沒問題,但user為啥不正確?
這就得精通Spring到底如何根據 @Value查詢值。
@Value的核心工作流程 DefaultListableBeanFactory#doResolveDependency
@Nullable public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { // ... Class<?> type = descriptor.getDependencyType(); // 尋找@Value Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { if (value instanceof String) { // 解析Value值 String strVal = resolveEmbeddedValue((String) value); BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); value = evaluateBeanDefinitionString(strVal, bd); } // 轉化Value解析的結果到裝配的類型 TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); try { return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor()); } catch (UnsupportedOperationException ex) {} } // ... }
@Value 的工作大體分為以下三個核心步驟。
1 尋找@Value
判斷這個屬性字段是否標記為@Value:
QualifierAnnotationAutowireCandidateResolver#findValue
- valueAnnotationType就是 @Value
2 解析@Value的字符串值
若一個字段標記了 @Value,則可拿到對應字符串值,然后根據字符串值解析,最終解析的結果可能是一個字符串or對象,取決于字符串怎么寫。
3 將解析結果轉化為待裝配的對象的類型
當拿到上一步生成的結果后,我們會發現可能和我們要裝配的類型不匹配。
比如定義的是UUID,而結果是個字符串,此時就會根據目標類型來尋找轉化器執行轉化:
分析可得問題關鍵在第二步,執行過程:
這里是在解析嵌入的值,替換掉占位符。使用PropertySourcesPlaceholderConfigurer根據PropertySources替換。
當使用 ${user}
獲取替換值時,最終執行的查找并非只在application.property文件。
可以發現如下“源”都是替換的依據:
而具體的查找執行,通過
PropertySourcesPropertyResolver#getProperty
獲取執行方式
在解析Value字符串有順序,源都存在CopyOnWriteArrayList,啟動時就被按序固定下來了,一個一個“源”順序查找,在其中一源找到后,就直接返回。
查看systemEnvironment源,發現剛好有個user和自定義的重合,且值不是admin。
所以這真是冤家路窄了,剛好系統環境變量(systemEnvironment)含同名配置。若沒有意識到它們的存在,起了同名字符串作為 @Value,就容易引發這類問題。
修正
避免使用同一個名稱,具體修改如下:
user.name=admin user.password=pass
其實還是不行。
在systemProperties這個PropertiesPropertySource源中剛好存在user.name,真是無巧不成書。所以命名時,我們一定要注意不僅要避免和環境變量沖突,也要注意避免和系統變量等其他變量沖突,才能從根本解決該問題。
Spring給我們提供了很多好用的功能,但是這些功能交織到一起后,就有可能讓我們誤入一些坑,只有了解它的運行方式,我們才能迅速定位問題、解決問題。
到此這篇關于Spring Bean 依賴注入常見錯誤的文章就介紹到這了,更多相關Spring Bean 依賴注入內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/qq_33589510/article/details/120246020