springboot中@Value的工作原理說明
我們知道springboot中的Bean組件的成員變量(屬性)如果加上了@Value注解,可以從有效的配置屬性資源中找到配置項進行綁定,那么這一切是怎么發(fā)生的呢?
下文將簡要分析一下@Value的工作原理。
springboot版本: springboot-2.0.6.RELEASE
概述
springboot啟動過程中,有兩個比較重要的過程,如下:
1 掃描,解析容器中的bean注冊到beanFactory上去,就像是信息登記一樣。
2 實例化、初始化這些掃描到的bean。
@Value的解析就是在第二個階段。BeanPostProcessor定義了bean初始化前后用戶可以對bean進行操作的接口方法,它的一個重要實現(xiàn)類AutowiredAnnotationBeanPostProcessor正如javadoc所說的那樣,為bean中的@Autowired和@Value注解的注入功能提供支持。
解析流程
調(diào)用鏈時序圖
@Value解析過程中的主要調(diào)用鏈,我用以下時序圖來表示:
這里先簡單介紹一下圖上的幾個類的作用。
AbstractAutowireCapableBeanFactory
: 提供了bean創(chuàng)建,屬性填充,自動裝配,初始胡。支持自動裝配構(gòu)造函數(shù),屬性按名稱和類型裝配。實現(xiàn)了AutowireCapableBeanFactory接口定義的createBean方法。
AutowiredAnnotationBeanPostProcessor
: 裝配bean中使用注解標(biāo)注的成員變量,setter方法, 任意的配置方法。比較典型的是@Autowired注解和@Value注解。
InjectionMetadata
: 類的注入元數(shù)據(jù),可能是類的方法或?qū)傩缘?,在AutowiredAnnotationBeanPostProcessor類中被使用。
AutowiredFieldElement
: 是AutowiredAnnotationBeanPostProcessor的一個私有內(nèi)部類,繼承InjectionMetadata.InjectedElement,描述注解的字段。
StringValueResolver
: 一個定義了處置字符串值的接口,只有一個接口方法resolveStringValue,可以用來解決占位符字符串。本文中的主要實現(xiàn)類在PropertySourcesPlaceholderConfigurer#processProperties方法中通過lamda表達式定義的。供ConfigurableBeanFactory類使用。
PropertySourcesPropertyResolver
: 屬性資源處理器,主要功能是獲取PropertySources屬性資源中的配置鍵值對。
PropertyPlaceholderHelper
: 一個工具類,用來處理帶有占位符的字符串。形如${name}的字符串在該工具類的幫助下,可以被用戶提供的值所替代。替代途經(jīng)可能通過Properties實例或者PlaceholderResolver(內(nèi)部定義的接口)。
PropertyPlaceholderConfigurerResolver
: 上一行所說的PlaceholderResolver接口的一個實現(xiàn)類,是PropertyPlaceholderConfigurer類的一個私有內(nèi)部類。實現(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è)置屬性字段值。 for (BeanPostProcessor bp : getBeanPostProcessors()) { // AutowiredAnnotationBeanPostProcessor會進入此分支 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逐個裝配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解析屬性值
/** * 一個參數(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)用本方法,因為屬性鍵中可能仍然有占位符 placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // Now obtain the value for the fully resolved key... // 獲取屬性鍵placeholder對應(yīng)的屬性值 String propVal = placeholderResolver.resolvePlaceholder(placeholder); // 此處邏輯是當(dāng)company.ceo=${bi:li}時,company.ceo最終被li所替代的原因 // 所以配置文件中,最好不要出現(xiàn)類似${}的東西,因為它本身就會被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的實例化、初始化階段完成的。以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java 8跳過本次循環(huán),繼續(xù)執(zhí)行以及跳出循環(huán),終止循環(huán)的代碼實例
今天小編就為大家分享一篇關(guān)于Java 8跳過本次循環(huán),繼續(xù)執(zhí)行以及跳出循環(huán),終止循環(huán)的代碼實例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-10-10Java 后端開發(fā)中Tomcat服務(wù)器運行不了的五種解決方案
tomcat是在使用Java編程語言開發(fā)服務(wù)端技術(shù)使用最廣泛的服務(wù)器之一,但經(jīng)常在開發(fā)項目的時候會出現(xiàn)運行不了的情況,這里總結(jié)出幾種能解決的辦法2021-10-10SpringBoot中實現(xiàn)@Scheduled動態(tài)定時任務(wù)
SpringBoot中的@Scheduled注解為定時任務(wù)提供了一種很簡單的實現(xiàn),本文主要介紹了SpringBoot中實現(xiàn)@Scheduled動態(tài)定時任務(wù),具有一定的參考價值,感興趣的可以了解一下2024-01-01使用@TableField(updateStrategy=FieldStrategy.IGNORED)遇到的坑記錄
這篇文章主要介紹了使用@TableField(updateStrategy=FieldStrategy.IGNORED)遇到的坑及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11Maven項目部署到服務(wù)器設(shè)置訪問路徑以及配置虛擬目錄的方法
今天小編就為大家分享一篇關(guān)于Maven項目部署到服務(wù)器設(shè)置訪問路徑以及配置虛擬目錄的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-02-02