為什麼 @Value 可以獲取配置中心的值?
阿新 • • 發佈:2020-11-24
hello,大家好,我是小黑,好久不見~~
> 這是關於配置中心的系列文章,應該會分多篇釋出,內容大致包括:
>
> 1、Spring 是如何實現 @Value 注入的
>
> 2、一個簡易版配置中心的關鍵技術
>
> 3、開源主流配置中心相關技術
# @Value 注入過程
從一個最簡單的程式開始:
```java
@Configuration
@PropertySource("classpath:application.properties")
public class ValueAnnotationDemo {
@Value("${username}")
private String username;
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ValueAnnotationDemo.class);
System.out.println(context.getBean(ValueAnnotationDemo.class).username);
context.close();
}
}
```
application.properties 檔案內容:
```properties
username=coder-xiao-hei
```
由 `AutowiredAnnotationBeanPostProcessor` 負責來處理 `@Value` ,此外該類還負責處理 `@Autowired` 和 `@Inject`。
![AutowiredAnnotationBeanPostProcessor 構造器](https://cdn.jsdelivr.net/gh/shenjianeng/pictures@main/2020-11-21/1605959414107-image.png)
在 `AutowiredAnnotationBeanPostProcessor` 中有兩個內部類:`AutowiredFieldElement` 和 `AutowiredMethodElement`。
當前為 Field 注入,定位到 `AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject` 方法。
![AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject](https://cdn.jsdelivr.net/gh/shenjianeng/pictures@main/2020-11-21/1605943144376-image.png)
通過 debug 可知,整個呼叫鏈如下:
- `AutowiredFieldElement#inject`
- `DefaultListableBeanFactory#resolveDependency`
- `DefaultListableBeanFactory#doResolveDependency`
- `AbstractBeanFactory#resolveEmbeddedValue`
![DefaultListableBeanFactory#doResolveDependency](https://cdn.jsdelivr.net/gh/shenjianeng/pictures@main/2020-11-21/1605943359456-image.png)
通過上述的 debug 跟蹤發現可以通過呼叫 `ConfigurableBeanFactory#resolveEmbeddedValue` 方法可以獲取佔位符的值。
![ConfigurableBeanFactory#resolveEmbeddedValue](https://cdn.jsdelivr.net/gh/shenjianeng/pictures@main/2020-11-21/1605943414511-image.png)
這裡的 `resolver` 是一個 lambda表示式,繼續 debug 我們可以找到具體的執行方法:
![AbstractApplicationContext#finishBeanFactoryInitialization](https://cdn.jsdelivr.net/gh/shenjianeng/pictures@main/2020-11-21/1605943448024-image.png)
到此,我們簡單總結下:
1. `@Value` 的注入由 `AutowiredAnnotationBeanPostProcessor` 來提供支援
2. 在 `AutowiredAnnotationBeanPostProcessor` 中通過呼叫 `ConfigurableBeanFactory#resolveEmbeddedValue` 來獲取佔位符具體的值
3. `ConfigurableBeanFactory#resolveEmbeddedValue` 其實是委託給了 `ConfigurableEnvironment` 來實現
## Spring Environment
### Environment 概述
> https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-environment
>
> The Environment interface is an abstraction integrated in the container that models two key aspects of the application environment: profiles and properties.
>
> A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or with annotations. The role of the Environment object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default.
>
> Properties play an important role in almost all applications and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Map objects, and so on. The role of the Environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.
Environment 是對 profiles 和 properties 的抽象:
- 實現了對屬性配置的統一儲存,同時 properties 允許有多個來源
- 通過 Environment profiles 來實現條件化裝配 Bean
現在我們主要來關注 Environment 對 properties 的支援。
### StandardEnvironment
下面,我們就來具體看一下 `AbstractApplicationContext#finishBeanFactoryInitialization` 中的這個 lambda 表示式。
```java
strVal -> getEnvironment().resolvePlaceholders(strVal)
```
首先,通過 `AbstractApplicationContext#getEnvironment` 獲取到了 `ConfigurableEnvironment` 的例項物件,這裡建立的其實是 ` StandardEnvironment` 例項物件。
在 ` StandardEnvironment` 中,預設添加了兩個自定義的屬性源,分別是:systemEnvironment 和 systemProperties。
![StandardEnvironment](https://cdn.jsdelivr.net/gh/shenjianeng/pictures@main/2020-11-21/1605944782556-image.png)
也就是說,`@Value` 預設是可以注入 system properties 和 system environment 的。
## PropertySource
` StandardEnvironment` 繼承了 `AbstractEnvironment` 。
在 `AbstractEnvironment` 中的屬性配置被存放在 `MutablePropertySources` 中。同時,屬性佔位符的資料也來自於此。
![AbstractEnvironment 成員變數](https://cdn.jsdelivr.net/gh/shenjianeng/pictures@main/2020-11-21/1605947633857-image.png)
`MutablePropertySources` 中存放了多個 `PropertySource` ,並且這些 `PropertySource` 是有順序的。
![MutablePropertySources#get](https://cdn.jsdelivr.net/gh/shenjianeng/pictures@main/2020-11-21/1605947790064-image.png)
`PropertySource` 是 Spring 對配置屬性源的抽象。
![PropertySource](https://cdn.jsdelivr.net/gh/shenjianeng/pictures@main/2020-11-21/1605947557860-image.png)
name 表示當前屬性源的名稱。source 存放了當前的屬性。
讀者可以自行檢視一下最簡單的基於 `Map` 的實現:`MapPropertySource `。
### 配置屬性源
有兩種方式可以進行屬性源配置:使用 `@PropertySource` 註解,或者通過 `MutablePropertySources` 的 API。例如:
```java
@Configuration
@PropertySource("classpath:application.properties")
public class ValueAnnotationDemo {
@Value("${username}")
private String username;
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ValueAnnotationDemo.class);