一、背景
之前專案中用到了Apollo
配置中心,對接Apollo配置中心後,配置中心的屬性就可以在程式中使用了,那麼這個是怎麼實現的呢?配置中心的屬性又是何時載入到程式中的呢?那麼我們如果找到了這個是怎麼實現的是否就可以 從任何地方載入配置屬性
、配置屬性的加解密功能呢
?
二、需求
從上圖中得知,我們的需求很簡單,即我們自己定義的屬性需要比配置檔案中的優先順序更高。
三、分析
1、什麼時候向SpringBoot中加入我們自己的配置屬性
當我們想在Bean中使用配置屬性時,那麼我們的配置屬性必須在Bean例項化之前就放入到Spring到Environment中。即我們的介面需要在 application context refreshed
之前進行呼叫,而 EnvironmentPostProcessor
正好可以實現這個功能。
2、獲取配置屬性的優先順序
我們知道在 Spring中獲取屬性是有優先順序的。
比如我們存在如下配置屬性 username
├─application.properties
│ >> username=huan
├─application-dev.properties
│ >> username=huan.fu
那麼此時 username
的值是什麼呢?此處借用 Apollo
的一張圖來說解釋一下這個問題。
參考連結:https://www.apolloconfig.com/#/zh/design/apollo-design
Spring從3.1版本開始增加了ConfigurableEnvironment
和PropertySource
:
ConfigurableEnvironment
- Spring的ApplicationContext會包含一個Environment(實現ConfigurableEnvironment介面)
- ConfigurableEnvironment自身包含了很多個PropertySource
PropertySource
- 屬性源
- 可以理解為很多個Key - Value的屬性配置
由上方的原理圖可知,key
在最開始出現的PropertySource
中的優先順序更高,上面的例子在SpringBoot
中username
的值為huan.fu
。
3、何時加入我們自己的配置
由第二步 獲取配置屬性的優先順序
可知,PropertySource
越靠前越先執行,那麼要我們配置生效,就必須放在越前面越好。
由上圖可知,SpringBoot載入各種配置是通過EnvironmentPostProcessor
來實現的,而具體的實現是ConfigDataEnvironmentPostProcessor
來實現的。那麼我們自己編寫一個EnvironmentPostProcessor
的實現類,然後在ConfigDataEnvironmentPostProcessor
後執行,並加入到 Environment
中的第一位即可。
四、實現
1、引入SpringBoot依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.huan.springcloud</groupId>
<artifactId>springboot-extension-point</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-extension-point</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
2、在application.properties中配置屬性
vim application.properties
username=huan
3、編寫自定義屬性並加入Spring Environment中
注意:
1、如果發現程式中日誌沒有輸出,檢查是否使用了slf4j
輸出日誌,此時因為日誌系統未初始化無法輸出日誌。解決方法如下:
SpringBoot版本
>= 2.4 可以參考上圖中的使用 DeferredLogFactory 來輸出日誌
< 2.4
1、參考如下連結 https://stackoverflow.com/questions/42839798/how-to-log-errors-in-a-environmentpostprocessor-execution
2、核心程式碼:
@Component
public class MyEnvironmentPostProcessor implements
EnvironmentPostProcessor, ApplicationListener<ApplicationEvent> {
private static final DeferredLog log = new DeferredLog();
@Override
public void postProcessEnvironment(
ConfigurableEnvironment env, SpringApplication app) {
log.error("This should be printed");
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
log.replayTo(MyEnvironmentPostProcessor.class);
}
}
4、通過SPI使自定義的配置生效
1、在 src/main/resources
下新建META-INF/spring.factories
檔案
2、配置
org.springframework.boot.env.EnvironmentPostProcessor=\
com.huan.springcloud.extensionpoint.environmentpostprocessor.CustomEnvironmentPostProcessor
5、編寫測試類,輸出定義的 username 屬性的值
@Component
public class PrintCustomizeEnvironmentProperty implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(PrintCustomizeEnvironmentProperty.class);
@Value("${username}")
private String userName;
@Override
public void run(ApplicationArguments args) {
log.info("獲取到的 username 的屬性值為: {}", userName);
}
}
6、執行結果
五、注意事項
1、日誌無法輸出
參考上方的 3、編寫自定義屬性並加入Spring Environment中
提供的解決方案。
2、配置沒有生效
- 檢查
EnvironmentPostProcessor
的優先順序,看看是否@Order
或者Ordered
返回的優先順序值不對。 - 看看別的地方是否實現了
EnvironmentPostProcessor
或ApplicationContextInitializer
或BeanFactoryPostProcessor
或BeanDefinitionRegistryPostProcessor
等這些介面,在這個裡面修改了PropertySource
的順序。 - 理解 Spring 獲取獲取屬性的順序 參考
2、獲取配置屬性的優先順序
3、日誌系統如何初始化
如下程式碼初始化日誌系統
org.springframework.boot.context.logging.LoggingApplicationListener
六、完整程式碼
七、參考連結
1、https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloApplicationContextInitializer.java
2、https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesProcessor.java
3、https://www.apolloconfig.com/#/zh/design/apollo-design