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

關(guān)于Spring中@Value注解使用和源碼分析

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

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);
	}
}

結(jié)果如下:

bean.name = abc

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

2、@Value 注解源碼分析

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

簡(jiǎn)單分析有三個(gè)步驟,如下:

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

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

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

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

Spring 提供很多 BeanFactoryPostProcessor、BeanPostProcessor 接口作為擴(kuò)展,從而使 Spring 非常強(qiáng)大,因?yàn)槲覀儗傩再x值相當(dāng)于是在實(shí)例化之后的事,所以這里的 @Value 解析就是 BeanPostProcessor 接口的應(yīng)用啦!

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

可以看到 Spring 是在實(shí)例化之后開(kāi)始去收集的,這個(gè)時(shí)序非常重要,一定要注意

然后進(jìn)入 AutowiredAnnotationBeanPostProcessor 類(lèi)核心部分,如下:

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

進(jìn)入 buildAutowiringMetadata() 方法內(nèi)部,如下:

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

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

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

AutowiredAnnotationBeanPostProcessor 類(lèi)創(chuàng)建的時(shí)候,Spring 就默認(rèn)往 autowiredAnnotationTypes 容器中添加兩個(gè)元素,如下:

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

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

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

進(jìn)入屬性填充的方法,源碼如下:

然后進(jìn)入到 AutowiredAnnotationBeanPostProcessor 類(lèi)中,源碼如下:

注意此時(shí)的 injectionMetadataCache 緩存中早已經(jīng)有值了,因?yàn)榍懊嫖覀兙鸵呀?jīng)收集完成了 @Value 修飾的屬性,所以這里直接從緩存中就可以獲取到。

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

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

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

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

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

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

注意這里是函數(shù)式寫(xiě)法,傳入一個(gè)方法體 this::getPropertyAsRawString,后面會(huì)回調(diào)到這里。


當(dāng)調(diào)用 resolvePlaceholder() 方法時(shí),回調(diào)到 getPropertyAsRawString() 方法,源碼如下:

可以看到最終會(huì)調(diào)用到 getProperty() 方法獲取到對(duì)應(yīng) key = apple.name 的值

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

propertySources 這里會(huì)有三個(gè),如下所示:

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

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

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

2.4、理解 PropertySource 和 MutablePropertySource

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

public abstract class PropertySource<T> {

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

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

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

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

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

看完這個(gè) PropertySource 類(lèi)的結(jié)構(gòu),我們看看上面三個(gè)類(lèi)中封裝的屬性到底是啥?如下所示:

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

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

  • ResourcePropertySource:封裝 application.properties、xml 中鍵值對(duì)

理解了 PropertySource 這個(gè)類(lèi)之后,在來(lái)理解 MutablePropertySource 就非常容易。

先來(lái)看看 MutablePropertySource 的源碼,如下:

public class MutablePropertySources {

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

}

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

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

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

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

輸出結(jié)果:

name = /Users/gongwm

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

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

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

直接進(jìn)入源碼分析,如下:


然后再 StandardEnvironment 的構(gòu)造方法中,隱式調(diào)用父類(lèi) AbstractEnvironment 的構(gòu)造方法,源碼如下:

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

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

對(duì)于自定義的配置文件在 ConfigurationClassPostProcessor 類(lèi)中被加載,源碼如下:

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

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

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

借助 BeanDefinitionRegistryPostProcessor 類(lèi)來(lái)實(shí)現(xiàn)這個(gè)功能,如下所示:

@Component
public class AppendAttrToEnvironment implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware {

	private ResourceLoader resourceLoader;

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

		// 創(chuàng)建第一個(gè)資源文件,其中有個(gè)屬性 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 資源文件管家中,注意是添加到最前面了,這樣就會(huì)被最先加載
		propertySources.addFirst(localResource);
	}

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

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

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

key666 = 6666key777 = ggg

然后在一個(gè) Apple 類(lèi)中去獲取對(duì)應(yīng)的值,如下所示:

@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);
	}
}

輸出結(jié)果如下:

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

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

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論