springboot中@Value的工作原理
我們知道springboot中的Bean元件的成員變數(屬性)如果加上了@Value
註解,可以從有效的配置屬性資源中找到配置項進行繫結,那麼這一切是怎麼發生的呢?下文將簡要分析一下@Value的工作原理。
springboot版本: springboot-2.0.6.RELEASE
概述
springboot啟動過程中,有兩個比較重要的過程,如下:
1 掃描,解析容器中的bean註冊到beanFactory上去,就像是資訊登記一樣。
2 例項化、初始化這些掃描到的bean。
@Value
的解析就是在第二個階段。BeanPostProcessor
定義了bean初始化前後使用者可以對bean進行操作的介面方法,它的一個重要實現類AutowiredAnnotationBeanPostProcessor
@Autowired
和@Value
註解的注入功能提供支援。
解析流程
呼叫鏈時序圖
@Value
解析過程中的主要呼叫鏈,我用以下時序圖來表示:
這裡先簡單介紹一下圖上的幾個類的作用。
AbstractAutowireCapableBeanFactory: 提供了bean建立,屬性填充,自動裝配,初始胡。支援自動裝配建構函式,屬性按名稱和型別裝配。實現了AutowireCapableBeanFactory
介面定義的createBean
方法。
AutowiredAnnotationBeanPostProcessor: 裝配bean中使用註解標註的成員變數,setter方法, 任意的配置方法。比較典型的是@Autowired
@Value
註解。
InjectionMetadata: 類的注入元資料,可能是類的方法或屬性等,在AutowiredAnnotationBeanPostProcessor類中被使用。
AutowiredFieldElement: 是AutowiredAnnotationBeanPostProcessor
的一個私有內部類,繼承InjectionMetadata.InjectedElement
,描述註解的欄位。
StringValueResolver: 一個定義了處置字串值的介面,只有一個介面方法resolveStringValue
,可以用來解決佔位符字串。本文中的主要實現類在PropertySourcesPlaceholderConfigurer#processProperties
ConfigurableBeanFactory
類使用。
PropertySourcesPropertyResolver: 屬性資源處理器,主要功能是獲取PropertySources
屬性資源中的配置鍵值對。
PropertyPlaceholderHelper: 一個工具類,用來處理帶有佔位符的字串。形如${name}的字串在該工具類的幫助下,可以被使用者提供的值所替代。替代途經可能通過Properties
例項或者PlaceholderResolver
(內部定義的介面)。
PropertyPlaceholderConfigurerResolver: 上一行所說的PlaceholderResolver
介面的一個實現類,是PropertyPlaceholderConfigurer
類的一個私有內部類。實現方法resolvePlaceholder
中呼叫了外部類的resolvePlaceholder
方法。
呼叫鏈說明
這裡主要介紹一下呼叫鏈中的比較重要的方法。
AbstractAutowireCapableBeanFactory#populateBean方法用於填充bean屬性,執行完後可獲取屬性裝配後的bean。
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
...
if (hasInstAwareBpps) {
// 遍歷所有InstantiationAwareBeanPostProcessor例項設定屬性欄位值。
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// AutowiredAnnotationBeanPostProcessor會進入此分支
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
//上行程式碼執行後,bw.getWrappedInstance()就得到了@Value註解裝配屬性後的bean了
if (pvs == null) {
return;
}
}
}
}
...
}
InjectionMetadata#inject逐個裝配bean的配置屬性。
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Collection<InjectedElement> checkedElements = this.checkedElements;
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
// 依次注入屬性
for (InjectedElement element : elementsToIterate) {
if (logger.isDebugEnabled()) {
logger.debug("Processing injected element of bean '" + beanName + "': " + element);
}
element.inject(target, beanName, pvs);
}
}
}
PropertyPlaceholderHelper#parseStringValue解析屬性值
/**
* 一個引數示例 value = "${company.ceo}"
*
*/
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(value);
// this.placeholderPrefix = "${"
int startIndex = value.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
// 佔位符的結束位置,以value = "${company.ceo}"為例,endIndex=13
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
// 獲取{}裡的真正屬性名稱,此例為"company.ceo"
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
// 遞迴呼叫本方法,因為屬性鍵中可能仍然有佔位符
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
// 獲取屬性鍵placeholder對應的屬性值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
// 此處邏輯是當company.ceo=${bi:li}時,company.ceo最終被li所替代的原因
// 所以配置檔案中,最好不要出現類似${}的東西,因為它本身就會被spring框架所解析
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
// 將${company.ceo}替換為li
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
總結
@Value
註解標註的bean屬性裝配是依靠AutowiredAnnotationBeanPostProcessor
在bean的例項化、初始化階段完成的。