SpringBoot擴(kuò)展點(diǎn)EnvironmentPostProcessor實(shí)例詳解
一、背景
之前項(xiàng)目中用到了Apollo配置中心,對(duì)接Apollo配置中心后,配置中心的屬性就可以在程序中使用了,那么這個(gè)是怎么實(shí)現(xiàn)的呢?配置中心的屬性又是何時(shí)加載到程序中的呢?那么我們?nèi)绻业搅诉@個(gè)是怎么實(shí)現(xiàn)的是否就可以 從任何地方加載配置屬性、配置屬性的加解密功能呢?
二、需求

從上圖中得知,我們的需求很簡(jiǎn)單,即我們自己定義的屬性需要比配置文件中的優(yōu)先級(jí)更高。
三、分析
1、什么時(shí)候向SpringBoot中加入我們自己的配置屬性
當(dāng)我們想在Bean中使用配置屬性時(shí),那么我們的配置屬性必須在Bean實(shí)例化之前就放入到Spring到Environment中。即我們的接口需要在 application context refreshed 之前進(jìn)行調(diào)用,而 EnvironmentPostProcessor 正好可以實(shí)現(xiàn)這個(gè)功能。
2、獲取配置屬性的優(yōu)先級(jí)
我們知道在 Spring中獲取屬性是有優(yōu)先級(jí)的。
比如我們存在如下配置屬性 username
├─application.properties │ >> username=huan ├─application-dev.properties │ >> username=huan.fu
那么此時(shí) username 的值是什么呢?此處借用 Apollo的一張圖來(lái)說(shuō)解釋一下這個(gè)問(wèn)題。
參考鏈接:https://www.apolloconfig.com/#/zh/design/apollo-design

Spring從3.1版本開(kāi)始增加了ConfigurableEnvironment和PropertySource:
ConfigurableEnvironment
- Spring的ApplicationContext會(huì)包含一個(gè)Environment(實(shí)現(xiàn)ConfigurableEnvironment接口)
- ConfigurableEnvironment自身包含了很多個(gè)PropertySource
PropertySource
- 屬性源
- 可以理解為很多個(gè)Key - Value的屬性配置
由上方的原理圖可知,key在最開(kāi)始出現(xiàn)的PropertySource中的優(yōu)先級(jí)更高,上面的例子在SpringBoot中username的值為huan.fu。
3、何時(shí)加入我們自己的配置
由第二步 獲取配置屬性的優(yōu)先級(jí) 可知,PropertySource 越靠前越先執(zhí)行,那么要我們配置生效,就必須放在越前面越好。

由上圖可知,SpringBoot加載各種配置是通過(guò)EnvironmentPostProcessor來(lái)實(shí)現(xiàn)的,而具體的實(shí)現(xiàn)是ConfigDataEnvironmentPostProcessor來(lái)實(shí)現(xiàn)的。那么我們自己編寫一個(gè)EnvironmentPostProcessor的實(shí)現(xiàn)類,然后在ConfigDataEnvironmentPostProcessor后執(zhí)行,并加入到 Environment中的第一位即可。

四、實(shí)現(xiàn)
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、如果發(fā)現(xiàn)程序中日志沒(méi)有輸出,檢查是否使用了slf4j輸出日志,此時(shí)因?yàn)槿罩鞠到y(tǒng)未初始化無(wú)法輸出日志。解決方法如下:
SpringBoot版本
>= 2.4 可以參考上圖中的使用 DeferredLogFactory 來(lái)輸出日志
< 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、通過(guò)SPI使自定義的配置生效
1、在 src/main/resources下新建META-INF/spring.factories文件

2、配置
org.springframework.boot.env.EnvironmentPostProcessor=\ com.huan.springcloud.extensionpoint.environmentpostprocessor.CustomEnvironmentPostProcessor
5、編寫測(cè)試類,輸出定義的 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、運(yùn)行結(jié)果

五、注意事項(xiàng)
1、日志無(wú)法輸出
參考上方的 3、編寫自定義屬性并加入Spring Environment中提供的解決方案。
2、配置沒(méi)有生效檢查
- 檢查EnvironmentPostProcessor的優(yōu)先級(jí),看看是否@Order或者Ordered返回的優(yōu)先級(jí)值不對(duì)。
- 看看別的地方是否實(shí)現(xiàn)了 EnvironmentPostProcessor或ApplicationContextInitializer或BeanFactoryPostProcessor或BeanDefinitionRegistryPostProcessor等這些接口,在這個(gè)里面修改了 PropertySource的順序。
- 理解 Spring 獲取獲取屬性的順序 參考 2、獲取配置屬性的優(yōu)先級(jí)
3、日志系統(tǒng)如何初始化
如下代碼初始化日志系統(tǒng)
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
4、解決EnvironmentPostProcessor中無(wú)法輸出日志
到此這篇關(guān)于SpringBoot擴(kuò)展點(diǎn)EnvironmentPostProcessor的文章就介紹到這了,更多相關(guān)SpringBoot擴(kuò)展點(diǎn)EnvironmentPostProcessor內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot?Test的webEnvironment源碼解讀
- springboot的EnvironmentPostProcessor接口方法源碼解析
- Spring運(yùn)行環(huán)境Environment的解析
- Spring?Boot讀取配置文件內(nèi)容的3種方式(@Value、Environment和@ConfigurationProperties)
- Spring之底層架構(gòu)核心概念Environment及用法詳解
- 詳解Spring中的Environment外部化配置管理
- 基于Spring Boot的Environment源碼理解實(shí)現(xiàn)分散配置詳解
- Spring之Environment類的使用方式
相關(guān)文章
Java實(shí)現(xiàn)音頻添加自定義時(shí)長(zhǎng)靜音的示例代碼
這篇文章主要介紹了一個(gè)Java工具類,可以實(shí)現(xiàn)給一個(gè)wav音頻添加自定義時(shí)長(zhǎng)靜音。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編學(xué)習(xí)一下2022-01-01
Spring Boot 中該如何防御計(jì)時(shí)攻擊
這篇文章主要介紹了Spring Boot 中該如何防御計(jì)時(shí)攻擊,幫助大家更好的使用spring boot框架,感興趣的朋友可以了解下2020-09-09
java中for循環(huán)執(zhí)行的順序圖文詳析
關(guān)于java的for循環(huán)想必大家非常熟悉,它是java常用的語(yǔ)句之一,這篇文章主要給大家介紹了關(guān)于java中for循環(huán)執(zhí)行順序的相關(guān)資料,需要的朋友可以參考下2021-06-06
Java concurrency集合之LinkedBlockingDeque_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
LinkedBlockingDeque是雙向鏈表實(shí)現(xiàn)的雙向并發(fā)阻塞隊(duì)列。該阻塞隊(duì)列同時(shí)支持FIFO和FILO兩種操作方式,即可以從隊(duì)列的頭和尾同時(shí)操作(插入/刪除);并且,該阻塞隊(duì)列是支持線程安全。2017-06-06
java WebSocket實(shí)現(xiàn)聊天消息推送功能
這篇文章主要為大家詳細(xì)介紹了java WebSocket實(shí)現(xiàn)聊天消息推送功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07

