欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關于Spring中@Value注解使用和源碼分析

 更新時間:2024年11月05日 14:45:48   作者:魔道不誤砍柴功  
通過深入分析@Value注解的使用和源碼,本文詳細解釋了Spring如何解析@Value注解并為屬性賦值,首先,Spring會解析并收集所有被@Value注解修飾的屬性,這一過程依賴于AutowiredAnnotationBeanPostProcessor類

1、@Value 注解使用

先配置本地 application.properties 如下:

apple.name=abc

代碼如下:

@PropertySource("application.properties")
public class Apple {
	@Value("${apple.name}")
	public String name;
}

@ComponentScan
public class AtValueTest {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AtValueTest.class);
		Apple bean = context.getBean(Apple.class);
		System.out.println("bean.name = " + bean.name);
	}
}

結果如下:

bean.name = abc

可以看到最終在 Apple 中可以獲取到配置文件中的值,那么 Spring 是怎樣解析獲取到該值的呢?下面開始分析下。

2、@Value 注解源碼分析

分析源碼之前,我么可以簡單思考下, Spring 會怎么樣去處理這個 @Value 注解的?然后再帶著我們的問題去看看 Spring 是不是和我們想的一樣呢?

簡單分析有三個步驟,如下:

  1. 首先 Spring 肯定要去解析收集該注解,找到被 @Value 注解修飾的所有屬性,注意此時屬性的值是一個占位符 ${apple.name},并不是真正的值
  2. 然后加載一個資源文件,可以是本地 application.properties、也可以是其他,比如 Environment 、xml
  3. 最后去判斷哪個類上的屬性有 @Value 修飾,就把占位符替換成資源文件中配置的值

在腦海中有了大致思路,再去追蹤源碼就事半功倍了。

2.1、Spring 解析并收集 @Value 修飾的屬性

Spring 解析收集 @Value 修飾的屬性和解析收集 @Autowired 注解一模一樣,懂這個,@Autowired 注解解析流程也就懂了。

Spring 提供很多 BeanFactoryPostProcessor、BeanPostProcessor 接口作為擴展,從而使 Spring 非常強大,因為我們屬性賦值相當于是在實例化之后的事,所以這里的 @Value 解析就是 BeanPostProcessor 接口的應用啦!

一個大家非常熟悉的類 AutowiredAnnotationBeanPostProcessor,Spring 的 DI(依賴注入) 就是這個 BeanPostProcessor 完成的,接下來看 Spring 源碼,如下:

可以看到 Spring 是在實例化之后開始去收集的,這個時序非常重要,一定要注意

然后進入 AutowiredAnnotationBeanPostProcessor 類核心部分,如下:

對過來的每個類進行篩選判斷是否有被 @Value、@Autowired 修飾的方法或者屬性,如果找到有,就會將這個類記錄下來,放到一個 injectionMetadataCache 緩存中,為后續(xù)的 DI 依賴注作準備,注意哦,解析并收集到的結果最終放到 Spring 的 injectionMetadataCache 緩存中

進入 buildAutowiringMetadata() 方法內部,如下:

獲取到這個類上所有的屬性,然后遍歷每個屬性,判斷是否有 @Value、@Autowired 修飾,如果有,直接封裝成 AutowiredFieldElement 對象,然后保存到一個名為 currElements List 容器中

最后在封裝到 InjectionMetadata 對象中,最終返回出去放到 injectionMetadataCache 緩存中保存,不用管他封裝到哪個對象,反正這里就是掃描并解析到了哪些屬性,或者方法后續(xù)需要做處理就可以了。

上面源碼中 findAutowiredAnnotation() 方法內部邏輯如下:

AutowiredAnnotationBeanPostProcessor 類創(chuàng)建的時候,Spring 就默認往 autowiredAnnotationTypes 容器中添加兩個元素,如下:

至此,解析收集 @Value 修飾的屬性已經完成,最終將收集到的結果放到了 injectionMetadataCache 緩存中保存,后續(xù)需要使用直接可以從這個緩存中獲取即可。

2.2、Spring 為 @Value 修飾屬性賦值

上面通過 postProcessMergedBeanDefinition() 方法收集好了 @Value 注解修飾的屬性,那么下面要做的就是去為這個屬性進行賦值。

進入屬性填充的方法,源碼如下:

然后進入到 AutowiredAnnotationBeanPostProcessor 類中,源碼如下:

注意此時的 injectionMetadataCache 緩存中早已經有值了,因為前面我們就已經收集完成了 @Value 修飾的屬性,所以這里直接從緩存中就可以獲取到。

然后進入 metadata.inject(bean, beanName, pvs) 代碼內部,如下:

最終通過 resolveFieldValue() 方法獲取到屬性值,然后通過反射 field.set() 方法給這個屬性賦值,如下:

至此,整個 @Value 的流程就算完成,下面就是對這個 resolveFieldValue() 方法進一步分析,看下是怎么獲取到屬性值的,是怎么樣將 $ 符號替換成解析成真正的值的

2.3、Spring $ 占位符替換成真正的值

繼續(xù)深入分析 resolveFieldValue() 方法,核心源碼如下:

下面這段邏輯是去解析 ${apple.name} 占位符的,這里面為什么需要遞歸 parseStringValue(),因為怕你出現(xiàn)這種形式的占位符 ${ ${apple.name} },最終獲取到 key = apple.name,然后拿著這個 key 就去資源文件(xml、application.properties、Environment 等)中查找是否配置了這個 key 的值

注意這里是函數(shù)式寫法,傳入一個方法體 this::getPropertyAsRawString,后面會回調到這里。


當調用 resolvePlaceholder() 方法時,回調到 getPropertyAsRawString() 方法,源碼如下:

可以看到最終會調用到 getProperty() 方法獲取到對應 key = apple.name 的值

propertySources 資源中獲取 key = apple.name 的值,只要在這里獲取到一個值就直接 return 出去即可

propertySources 這里會有三個,如下所示:

PropertiesPropertySource:封裝操作系統(tǒng)屬性鍵值對SystemEnvironmentPropertySource:封裝 JVM Environment 里面的鍵值對ResourcePropertySource:封裝 application.properties、xml 中鍵值對

然后 debug 發(fā)現(xiàn)最終是從 ResourcePropertySource 資源對象中獲取到 apple.name 對應的值 abc,最終將 ${apple.name} 替換成真正的值 abc,最終通過反射將該值 abc 賦值到 Apple 類中的 name 屬性上。

那么這里肯定有很多人會有這樣的疑問?這三個對象是從哪里來的呢?如果要想知道這個,需要下面一些知道做鋪墊,那么繼續(xù)往下看 !

2.4、理解 PropertySource 和 MutablePropertySource

在 Spring 中需要加載一些額外的配置文件,比如操作系統(tǒng)相關的配置,JVM 環(huán)境變量相關的配置,自定義配置文件等等。那么這些文件加載到代碼中可定要有一個類來封裝它,這個類就是 PropertySource,先來看看 PropertySource 的源碼如下:

public abstract class PropertySource<T> {

	protected final Log logger = LogFactory.getLog(getClass());
	
	// 給這個配置文件起個名字唄
	protected final String name;

    // 配置文件中所有的 key-value 鍵值對 
    // T 只要是 key-value 鍵值對即可,比如: Map、Properties 都可以
	protected final T source;

	public String getName() {
		return this.name;
	}

	public T getSource() {
		return this.source;
	}

    // 根據(jù) name 獲取到對應的配置文件
	@Nullable
	public abstract Object getProperty(String name);
}

看完這個 PropertySource 類的結構,我們看看上面三個類中封裝的屬性到底是啥?如下所示:

  • PropertiesPropertySource:封裝操作系統(tǒng)屬性鍵值對(os.name、file.encoding、user.name 等等)

  • SystemEnvironmentPropertySource:封裝 JVM Environment 里面的鍵值對(PATH、JAVA_HOME)

  • ResourcePropertySource:封裝 application.properties、xml 中鍵值對

理解了 PropertySource 這個類之后,在來理解 MutablePropertySource 就非常容易。

先來看看 MutablePropertySource 的源碼,如下:

public class MutablePropertySources {

	private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();

}

從源碼中可以看到,就是做了一個收集,將所有的 PropertySource 收集到一個 propertySourceList 容器中進行管理,至于為什么這樣做呢?

個人理解是為了更方便的查找屬性,將所有的資源文件匯集到一起,然后想要找一個 key = apple.name 就可以直接遍歷一下所有的資源文件,看下這個 key 在哪個資源文件中能夠找到,找到立即返回。

但是此時就會存在一個問題,那就是文件的加載順序,可以發(fā)現(xiàn)最先加載 PropertiesPropertySource、其次 SystemEnvironmentPropertySource,最后才是自定義的配置文件 ResourcePropertySource!比如,我們在 application.properties 中配置 user.home=abc 如下所示:

user.home=abc
@Component
@PropertySource("application.properties")
public class Apple {
	@Value("${user.home}")
	public String name;
}

輸出結果:

name = /Users/gongwm

并不是 abc,因為 PropertiesPropertySource 優(yōu)先于 ResourcePropertySource 被加載。

下面這個源碼是獲取資源文件,可以發(fā)現(xiàn)只要獲取到就會立即 return,所以最終決定權交給了往 propertySources 容器添加的順序決定,那么我們來看看上面三個文件分別是在什么時候方進去的?

2.5、Spring 資源文件裝載源碼分析?

直接進入源碼分析,如下:


然后再 StandardEnvironment 的構造方法中,隱式調用父類 AbstractEnvironment 的構造方法,源碼如下:

可以發(fā)現(xiàn)在這里直接 new 創(chuàng)建了 MutablePropertySources 對象

最終可以發(fā)現(xiàn)這兩個 systemProperties、systemEnvironment 資源文件都是在這里被加載的,添加到了 MutablePropertySources 對象中

對于自定義的配置文件在 ConfigurationClassPostProcessor 類中被加載,源碼如下:

最終就是在這里被加載進去的,注意這個添加方法是 addLast() 也就是往后面追加,這個方法就體現(xiàn)了這些資源文件的加載順序,那么有 addLast() ,必然有 addFirst() 等等 API,此時 propertySourceList 容器中就已經保存了三個資源文件,并且順序是這樣的PropertiesPropertySource(優(yōu)先級最高) -> SystemEnvironmentPropertySource -> ResourcePropertySource (優(yōu)先級最低)

最后有一點要注意,不要把 MutablePropertySourcesMutablePropertyValues 搞混了,兩完全不是一一碼事!具體想看 MutablePropertyValues 是啥,可以轉到另一篇文章!

2.6、在 Environment 中添加自定義屬性

借助 BeanDefinitionRegistryPostProcessor 類來實現(xiàn)這個功能,如下所示:

@Component
public class AppendAttrToEnvironment implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware {

	private ResourceLoader resourceLoader;

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		// 從容器中獲取到 Environment 對象
		StandardEnvironment bean = (StandardEnvironment)beanFactory.getBean(Environment.class);
		// 然后再獲取到 MutablePropertySources 資源文件管家(它會收集打包所有的資源文件)
		MutablePropertySources propertySources = bean.getPropertySources();

		// 創(chuàng)建第一個資源文件,其中有個屬性 key666 值為 hangman
		Properties properties = new Properties();
		properties.put("key666", "hangman");
		PropertiesPropertySource propertiesSource = new PropertiesPropertySource("myPropertySource",properties);
		// 添加到 MutablePropertySources 資源文件管家中,注意使用 addLast() 添加的,也就是往后追加
        propertySources.addLast(propertiesSource);


		// 創(chuàng)建第二資源文件,看下面的 abc.properties 配置文件
		Resource resource = resourceLoader.getResource("abc.properties");
		ResourcePropertySource localResource = new ResourcePropertySource("myResourceSource",resource);
		// 添加到 MutablePropertySources 資源文件管家中,注意是添加到最前面了,這樣就會被最先加載
		propertySources.addFirst(localResource);
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}
}

創(chuàng)建一個 abc.properties 配置文件如下所示:

key666 = 6666key777 = ggg

然后在一個 Apple 類中去獲取對應的值,如下所示:

@Component
@PropertySource("application.properties")
public class Apple implements EnvironmentAware {
	@Value("${apple.name}")
	public String name;

	@Override
	public void setEnvironment(Environment environment) {
		String property = environment.getProperty("apple.name");
		String key666 = environment.getProperty("key666");
		String key777 = environment.getProperty("key777");
		System.out.println("key666 = " + key666);
		System.out.println("key777 = " + key777);
		System.out.println("name = " + name);
		System.out.println("property = " + property);
	}
}

輸出結果如下:

key666 = 6666
key777 = ggg
name = abc
property = abc
bean.name = abc

這里會發(fā)現(xiàn)一個問題,key = key666 的這個鍵值對,在 propertiesSource 資源和 localResource 資源文件同時出現(xiàn),最終因為 localResource 資源是通過 addFirst() 方法添加到 MutablePropertySources 管家容器最前面,優(yōu)先生效。

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • 使用Java實現(xiàn)一個解析CURL腳本小工具

    使用Java實現(xiàn)一個解析CURL腳本小工具

    文章介紹了如何使用Java實現(xiàn)一個解析CURL腳本的工具,該工具可以將CURL腳本中的Header解析為KV Map結構,獲取URL路徑、請求類型,解析URL參數(shù)列表和Body請求體,感興趣的小伙伴跟著小編一起來看看吧
    2025-02-02
  • Springboot中yml文件沒有葉子圖標的解決

    Springboot中yml文件沒有葉子圖標的解決

    這篇文章主要介紹了Springboot中yml文件沒有葉子圖標的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Logger.getLogger()與LogFactory.getLog()的區(qū)別詳解

    Logger.getLogger()與LogFactory.getLog()的區(qū)別詳解

    LogFactory來自common-logging包。如果用LogFactory.getLog,你可以用任何實現(xiàn)了通用日志接口的日志記錄器替換log4j,而程序不受影響
    2013-09-09
  • Mybatis-plus與Mybatis依賴沖突問題解決方法

    Mybatis-plus與Mybatis依賴沖突問題解決方法

    ,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧這篇文章主要介紹了Mybatis-plus與Mybatis依賴沖突問題解決方法
    2021-04-04
  • Java使用OSS實現(xiàn)上傳文件功能

    Java使用OSS實現(xiàn)上傳文件功能

    這篇文章主要為大家詳細介紹了Java如何使用OSS實現(xiàn)上傳文件功能,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以了解一下
    2024-01-01
  • 完美解決idea沒有tomcat server選項的問題

    完美解決idea沒有tomcat server選項的問題

    這篇文章主要介紹了完美解決idea沒有tomcat server選項的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • Windows編寫jar啟動腳本和關閉腳本的操作方法

    Windows編寫jar啟動腳本和關閉腳本的操作方法

    腳本文件,通常放入/bin目錄下,編寫啟動腳本需要保證能夠識別到對應的jar文件,其次需要保證能夠識別到/config中的配置文件信息,這篇文章主要介紹了Windows編寫jar啟動腳本和關閉腳本的操作方法,需要的朋友可以參考下
    2022-12-12
  • Java concurrency集合之CopyOnWriteArraySet_動力節(jié)點Java學院整理

    Java concurrency集合之CopyOnWriteArraySet_動力節(jié)點Java學院整理

    CopyOnWriteArraySet基于CopyOnWriteArrayList實現(xiàn),其唯一的不同是在add時調用的是CopyOnWriteArrayList的addIfAbsent(若沒有則增加)方法
    2017-06-06
  • Java實現(xiàn)九九乘法表的小例子

    Java實現(xiàn)九九乘法表的小例子

    九九乘法表一般為三角形,每個數(shù)分別和從1到自身的數(shù)相乘然后把結果列出來,即要用到兩層循環(huán),外層是從1到9for(i=1;i<=9;i++),內層是當前數(shù)和從1到自身相乘for(j=1;j<=i;j++)
    2013-09-09
  • java?數(shù)組實現(xiàn)學生成績統(tǒng)計教程

    java?數(shù)組實現(xiàn)學生成績統(tǒng)計教程

    這篇文章主要介紹了java?數(shù)組實現(xiàn)學生成績統(tǒng)計教程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12

最新評論