一、背景
当前项目基于springBoot, 且配置中心使用的阿里云acm,但是配置中心暂未整合动态配置。
二、思路
首先整理了以下几点切入点:
- spring framework有一个@RefreshScope注解,用来做配置刷新。
- 阿里acm官方有文档帮助整合。
- 看看acm控制台都有哪些功能。
- 了解spring-actuator。
根据以上几个切入点开始整合。
三、接入过程
1. 问题
按照文档 的描述,只需要添加maven依赖,然后直接使用@RefreshScope就可以了。但是实际这样存在问题。
问题1: 在acm控制后台,只能查到推送轨迹,而没有监听查询记录。
问题2: 在acm控制后台修改后,client端配置并没有更新。
❓推测1: 我的应用部署在容器里,之后暴露端口到机器上,机器上有一层nginx代理,再上面还有一层LBS。会不会是acm server推过来压根就失败了,只是推送轨迹显示推送成功。
❓推测2:会不会是我的监听器注册失败,所以在acm server上没有监听查询记录。
2. 解决过程
1⃣️首先看一下spring-cloud-starter-acm里都有什么。
2⃣️里面直接就是starter相关的类。找到AcmAutoConfiguration类,看看里面都有什么。
3⃣️看一下这几个类的源码,果然在AcmContextRefresher类里看到了registerDiamondListener()方法,这个就是注册监听器的核心逻辑了。
4⃣️debug AcmContextRefresher类,看到当registerDiamondListenersForApplications里的acmPropertySourceRepository.getAll() size为0时,不会去调用registerDiamondListener()
5⃣️点进去debug一下,可以看到这里的逻辑是找到一个AcmPropertySource类型的源参数类后放到list,之后根据这个list来迭代,注册listener。现在的问题是找不到AcmPropertySource类型的类。
6⃣️acm阿里官网文档有说:
访问 ip+端口/actuator/acm 访问acm的Endpoint。
{
"runtime": {
"sources": [
{
"lastSynced": "",
"dataId": ""
}
],
"refreshHistory": [
]
},
"config": {
"group": "group",
"timeOut": 3000,
"endpoint": "acm.aliyun.com",
"namespace": "xxxxx",
"accessKey": "xxx",
"secretKey": "xxxx",
"ramRoleName": null,
"fileExtension": "yaml"
}
}
我的访问结果dataId、lastSynced、refreshHistory都没有值,而acm阿里官网是有值的。
阿里官网的acm长这样:
{
"runtime": {
"sources": [
{
"dataId": "com.alibaba.cloud.acm:sample-app.properties",
"lastSynced": "2017-10-10 10:46:27"
}
],
"refreshHistory": [
{
"timestamp": "2017-10-10 10:46:24",
"dataId": "com.alibaba.cloud.acm:sample-app.properties",
"md5": "8692ae986ec7bc345b3f0f4de602ff13"
}
]
},
"config": {
"group": "DEFAULT_GROUP",
"timeOut": 3000,
"endpoint": "xxx",
"namespace": "xxx",
"accessKey": "xxx",
"secretKey": "xxx"
}
}
仔细观察官网的结构,发现dataId跟我在acm控制台设置的dataId长的比较类似。脑瓜子一闪,这里的dataId是不是就是acm控制台配置的dataId。
7⃣️再次debug刚刚的acmPropertySourceRepository.getAll(),仔细看结果发现了dataId。
此时的PropertySource并不是CompositePropertySource,而是BootstrapPropertySource,所以这个result为空.
8⃣️明白以上之后,我直接重写掉这部分逻辑是不是就可以了。
3.重写方案
1⃣️重写AcmPropertySourceRepository
public class AcmCustomPropertySourceRepository extends AcmPropertySourceRepository {
private final ApplicationContext applicationContext;
public AcmCustomPropertySourceRepository(ApplicationContext applicationContext) {
super(applicationContext);
this.applicationContext = applicationContext;
}
public List<AcmPropertySource> getAll() {
List<AcmPropertySource> result = new ArrayList<>();
ConfigurableApplicationContext ctx = (ConfigurableApplicationContext)applicationContext;
for (PropertySource p : ctx.getEnvironment().getPropertySources()) {
if (p instanceof AcmPropertySource) {
result.add((AcmPropertySource)p);
} else if (p instanceof BootstrapPropertySource) {
BootstrapPropertySource bps = (BootstrapPropertySource)p;
PropertySource propertySource = bps.getDelegate();
if (propertySource instanceof AcmPropertySource) {
result.add((AcmPropertySource)propertySource);
}
}
}
return result;
}
}
2⃣️配置类
@Configuration
public class AcmCustomConfiguration implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Bean("acmCustomPropertySourceRepository")
@Primary
public AcmPropertySourceRepository acmPropertySourceRepository() {
return new AcmCustomPropertySourceRepository(applicationContext);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
debug看一下,这时候的result有值,并且listner成功注册。
校验:
1.再次访问: ip+端口/actuator/acm
{
"runtime": {
"sources": [
{
"lastSynced": "2020-08-05 17:35:02",
"dataId": "com.xxxxtest2.yaml"
}
],
"refreshHistory": [
{
"timestamp": "2020-08-05 17:39:38",
"dataId": "com.xxxxxtest2.yaml",
"md5": "20f6572e9f097d8cbf075f83eabe958e"
}
]
},
"config": {
"group": "group",
"timeOut": 3000,
"endpoint": "acm.aliyun.com",
"namespace": "xxxx",
"accessKey": "xxxx",
"secretKey": "xxxx",
"ramRoleName": null,
"fileExtension": "yaml"
}
}
2.在acm控制台修改配置, 已经看到了正确的监听记录。
四、使用
1. 使用@Value()
当使用@Value读取配置文件,且需要配置文件热更新时,需要在类上添加@RefreshScope注解。
例如:
@RequestMapping("/test")
@RestController
@RefreshScope
public class TestController {
@Value("${shareLink.joinUrl}")
private String str;
@GetMapping("/test")
public String test(){
return str;
}
}
2.使用@Bean
当使用@Bean读取一个配置的properties class,且需要配置的properties class热更新时,需要在@Bean的注解下面添加@RefreshScope注解。
例如:
@Getter
@Setter
@ConfigurationProperties(prefix = "aliyun.oss")
public class OssProperties {
private String endpoint;
private String regionId;
private String region;
private String accessKeyId;
private String accessKeySecret;
private String stsRoleArn;
private String bucket;
private String userId;
}
@Configuration
@EnableConfigurationProperties(OssProperties.class)
public class OssAutoConfiguration {
@Bean
@RefreshScope
public OSSClient ossClient(OssProperties ossProperties) {
CredentialsProvider credentialsProvider = new DefaultCredentialProvider(ossProperties.getAccessKeyId(),
ossProperties.getAccessKeySecret());
ClientConfiguration config = new ClientConfiguration();
return new OSSClient(ossProperties.getEndpoint(), credentialsProvider, config);
}
}
以上,搞定。