目录
一、适应场景
原始需求:配置内容为外部文件,文件出现变更时能够自行刷新spring容器中的bean。
此源码初衷为应用于敏感信息治理Vault,客户端能够随着vault密钥的轮转,在不重启的情况下自动刷新密钥。
二、制作依赖
方式:源码引入,通过archiaus加载动态配置,通过springcloud context刷新bean。
1、pom.xml添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.archaius</groupId>
<artifactId>archaius-core</artifactId>
<version>0.7.5</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
注意:springcloud相关依赖为选填,原先是springcloud项目的不需要引入springcloud相关依赖
2、初始化vault动态配置,并配置文件监听(此处为源码)
import com.netflix.config.ConcurrentCompositeConfiguration;
import com.netflix.config.ConfigurationManager;
import com.netflix.config.DynamicURLConfiguration;
import org.apache.commons.configuration.event.ConfigurationEvent;
import org.apache.commons.configuration.event.ConfigurationListener;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* Title: 动态加载vault配置文件,并刷新
* Description:
* author :xbl
* Date:2023/2/8
* Time:10:37
*/
@Configuration
public class VaultConfigProcessor {
private static final Log log = LogFactory.getLog(VaultConfigProcessor.class);
@Autowired
private ContextRefresher refresher;
@Value("${vault.secretPath}")
private String vaultFile;
@Value("${vault.secretKeys}")
private String secretKeys;
@PostConstruct
public void initVaultArchiausProcessor() {
try {
log.info("[VAULT-ARCHIAUS] - Dynamic property file is {}" + vaultFile);
DynamicURLConfiguration dynamicURLConfiguration = new DynamicURLConfiguration(10000, 10000, false, vaultFile);
ConcurrentCompositeConfiguration finalConfig = new ConcurrentCompositeConfiguration();
finalConfig.addConfiguration(dynamicURLConfiguration);
ConfigurationManager.install(finalConfig);
ConfigurationManager.getConfigInstance().addConfigurationListener(new VaultConfigurationListener());
} catch (RuntimeException e) {
log.error("###############");
log.error("[VAULT-ARCHIAUS] - project: initialization failed. Forcing shutdown now.");
log.error("###############");
e.printStackTrace();
System.exit(0);
}
}
class VaultConfigurationListener implements ConfigurationListener {
@Override
public void configurationChanged(ConfigurationEvent event) {
if (!event.isBeforeUpdate()) {
if (!StringUtils.isEmpty(event.getPropertyName())&&
secretKeys.contains(event.getPropertyName())) {
log.info("[VAULT-ARCHIAUS] - " + event.getPropertyName() + " will refreshing! ");
refresher.refresh();
}
}
}
}
}
3、使用配置热加载功能
1> 定位vault生成的配置文件地址,填写要求的静态配置到启动配置application.yml中
vault:
#动态配置目录,即vault目标文件地址
secretPath: file:/D:\project\src\main\resources\vault-secret.properties
#监听哪些Key触发刷新,逗号分隔
secretKeys: secretId,secretKey
2>vault-secret.properties(动态配置的真实内容,接入vault时会将配置落到此文件)
secretId=aaaaaaaavvvvvv
secretKey=acccccccckkkkk
3>客户端使用动态配置
bean初始化时使用archaius获取配置内容,添加@RefreshScope注解实现动态刷新,参考如下:
//bean对象案例
public class ClientEntity {
private String secretId;
public String getSecretId() {
return secretId;
}
public void setSecretId(String secretId) {
this.secretId = secretId;
}
}
//构建bean示例
@Bean
@RefreshScope
public ClientEntity clientEntity() {
//取账号密钥
String secretId = DynamicPropertyFactory.getInstance().getStringProperty("key", null).get();
ClientEntity entity = new ClientEntity();
entity.setSecretId(secretId);
return entity;
}
//使用单例bean
@Autowired
private ClientEntity clientEntity;
达到的效果:vault-secret.properties 触发变更时,spring容器内的 tencentClientEntity 也会更新 secretId,实现热加载。
对于使用者来说,只需要将密钥信息使用archiaus进行动态加载,并且加入@RefreshScope注解即可。
如果是非spring体系,不需要刷新bean,使用archiaus即可。
三、问题与思考
1、为什么不直接使用spring-cloud的动态配置加载能力,反而引入archiaus?
答:它需要在启动时指向配置文件,不够友好,我需要的是更灵活的外部配置;其次,它所提供配置的更新一般是集成actuator使用,需要对外暴露接口,需要手动触发更新,对于vault来说这种方式的接入太复杂,而archiaus能自己吃掉这个问题,解决耦合度的问题,同时使用起来更舒适。
2、为什么要使用springcloud去做bean的热更新?自己手动实现热加载不行吗?
答:原计划是手动刷新bean,从context获取到的bean能够实现热更新,这个已经跑通了,但是某些注解比如@autowired,它做的自动装配会有一层bean缓存,手动更新bean更新不了它,springcloud的RefreshScope就是用来处理这个问题的,如果自己去实现,也是参考cloud的方式。综合springcloud体系的覆盖面,决定直接使用springcloud解决这个问题。