springboot中@Value的工作原理說明
我們知道springboot中的Bean組件的成員變量(屬性)如果加上了@Value注解,可以從有效的配置屬性資源中找到配置項(xiàng)進(jìn)行綁定,那么這一切是怎么發(fā)生的呢?
下文將簡(jiǎn)要分析一下@Value的工作原理。
springboot版本: springboot-2.0.6.RELEASE
概述
springboot啟動(dòng)過程中,有兩個(gè)比較重要的過程,如下:
1 掃描,解析容器中的bean注冊(cè)到beanFactory上去,就像是信息登記一樣。
2 實(shí)例化、初始化這些掃描到的bean。
@Value的解析就是在第二個(gè)階段。BeanPostProcessor定義了bean初始化前后用戶可以對(duì)bean進(jìn)行操作的接口方法,它的一個(gè)重要實(shí)現(xiàn)類AutowiredAnnotationBeanPostProcessor正如javadoc所說的那樣,為bean中的@Autowired和@Value注解的注入功能提供支持。
解析流程
調(diào)用鏈時(shí)序圖
@Value解析過程中的主要調(diào)用鏈,我用以下時(shí)序圖來表示:

這里先簡(jiǎn)單介紹一下圖上的幾個(gè)類的作用。
AbstractAutowireCapableBeanFactory: 提供了bean創(chuàng)建,屬性填充,自動(dòng)裝配,初始胡。支持自動(dòng)裝配構(gòu)造函數(shù),屬性按名稱和類型裝配。實(shí)現(xiàn)了AutowireCapableBeanFactory接口定義的createBean方法。
AutowiredAnnotationBeanPostProcessor: 裝配bean中使用注解標(biāo)注的成員變量,setter方法, 任意的配置方法。比較典型的是@Autowired注解和@Value注解。
InjectionMetadata: 類的注入元數(shù)據(jù),可能是類的方法或?qū)傩缘?,在AutowiredAnnotationBeanPostProcessor類中被使用。
AutowiredFieldElement: 是AutowiredAnnotationBeanPostProcessor的一個(gè)私有內(nèi)部類,繼承InjectionMetadata.InjectedElement,描述注解的字段。
StringValueResolver: 一個(gè)定義了處置字符串值的接口,只有一個(gè)接口方法resolveStringValue,可以用來解決占位符字符串。本文中的主要實(shí)現(xiàn)類在PropertySourcesPlaceholderConfigurer#processProperties方法中通過lamda表達(dá)式定義的。供ConfigurableBeanFactory類使用。
PropertySourcesPropertyResolver: 屬性資源處理器,主要功能是獲取PropertySources屬性資源中的配置鍵值對(duì)。
PropertyPlaceholderHelper: 一個(gè)工具類,用來處理帶有占位符的字符串。形如${name}的字符串在該工具類的幫助下,可以被用戶提供的值所替代。替代途經(jīng)可能通過Properties實(shí)例或者PlaceholderResolver(內(nèi)部定義的接口)。
PropertyPlaceholderConfigurerResolver: 上一行所說的PlaceholderResolver接口的一個(gè)實(shí)現(xiàn)類,是PropertyPlaceholderConfigurer類的一個(gè)私有內(nèi)部類。實(shí)現(xiàn)方法resolvePlaceholder中調(diào)用了外部類的resolvePlaceholder方法。
調(diào)用鏈說明
這里主要介紹一下調(diào)用鏈中的比較重要的方法。
AbstractAutowireCapableBeanFactory#populateBean方法用于填充bean屬性,執(zhí)行完后可獲取屬性裝配后的bean。
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
...
if (hasInstAwareBpps) {
// 遍歷所有InstantiationAwareBeanPostProcessor實(shí)例設(shè)置屬性字段值。
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// AutowiredAnnotationBeanPostProcessor會(huì)進(jìn)入此分支
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
//上行代碼執(zhí)行后,bw.getWrappedInstance()就得到了@Value注解裝配屬性后的bean了
if (pvs == null) {
return;
}
}
}
}
...
}
InjectionMetadata#inject逐個(gè)裝配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解析屬性值
/**
* 一個(gè)參數(shù)示例 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) {
// 占位符的結(jié)束位置,以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.
// 遞歸調(diào)用本方法,因?yàn)閷傩枣I中可能仍然有占位符
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
// 獲取屬性鍵placeholder對(duì)應(yīng)的屬性值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
// 此處邏輯是當(dāng)company.ceo=${bi:li}時(shí),company.ceo最終被li所替代的原因
// 所以配置文件中,最好不要出現(xiàn)類似${}的東西,因?yàn)樗旧砭蜁?huì)被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();
}
總結(jié)
@Value注解標(biāo)注的bean屬性裝配是依靠AutowiredAnnotationBeanPostProcessor在bean的實(shí)例化、初始化階段完成的。以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Boot2.0實(shí)現(xiàn)靜態(tài)資源版本控制詳解
這篇文章主要給大家介紹了關(guān)于Spring Boot2.0實(shí)現(xiàn)靜態(tài)資源版本控制的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
詳解Spring MVC自動(dòng)為對(duì)象注入枚舉類型
本篇文章主要介紹了Spring MVC自動(dòng)為對(duì)象注入枚舉類型,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04
Struts2學(xué)習(xí)筆記(9)-Result配置全局結(jié)果集
這篇文章主要介紹Struts2中使用Result配置全局結(jié)果集的方法,希望能給大家做一個(gè)參考。2016-06-06
SpringBoot整合dataworks的實(shí)現(xiàn)過程
這篇文章主要介紹了SpringBoot整合dataworks的實(shí)現(xiàn)過程,實(shí)現(xiàn)主要是編寫工具類,如果需要?jiǎng)t可以配置成SpringBean,注入容器即可使用,需要的朋友可以參考下2022-08-08
vscode搭建java開發(fā)環(huán)境的實(shí)現(xiàn)步驟
本文主要介紹了vscode搭建java開發(fā)環(huán)境,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
JavaSwing坦克大戰(zhàn)游戲的設(shè)計(jì)和實(shí)現(xiàn)
JavaSwing坦克大戰(zhàn)游戲的設(shè)計(jì)要有圖形用戶界面,界面能夠反映游戲所有的細(xì)節(jié),在最終呈現(xiàn)的游戲中也要滿足所有需求,感興趣的小伙伴一起來看看吧2021-08-08
兩種Spring服務(wù)關(guān)閉時(shí)對(duì)象銷毀的實(shí)現(xiàn)方法
spring提供了兩種方式用于實(shí)現(xiàn)對(duì)象銷毀時(shí)去執(zhí)行的操作,本文主要為大家詳細(xì)介紹了這兩種方式的具體實(shí)現(xiàn),文中的示例代碼講解詳細(xì),希望對(duì)大家有所幫助2023-04-04

