20-SpringBoot外部配置文件热加载

image-20200708160944615

需求

系统遇到这样一个需求,线上环境在配置文件发生变动时,可以不用经过重启,通过刷新接口的方式得到配置文件的加载,主要目的是为了迅速部署,避免因手动重启,出现数据或任务丢失的问题。

外部配置

当启动 Spring Boot 项目的 jar 包时,可以通过--spring.config.location参数来指定外部配置文件的位置。例如,假设外部配置文件在/Users/user/config/application.yml,在命令行执行以下命令:

1
java -jar your-application.jar --spring.config.location=/Users/user/config/application.yml

还可以指定多个配置文件,使用逗号分隔。如:
--spring.config.location=/Users/user/config/application.yml,/Users/user/config/database.yml

这种方式在需要加载多个配置文件(如应用配置和数据库配置分开)时很有用。

实现

第一步,添加适合自己springboot版本的Springcloud context依赖,若Springboot版本低,maven可能会引不上高版本的context

1
2
3
4
5
6
7
8
9
10
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
</dependencies>

这里参考 Spring-Cloud-Config 主要借助 org.springframework.cloud.context.refresh.ContextRefresher 这个类来实现配置刷新,因此需要对 SpringBoot 项目做一点修改。

代码演示

配置文件: application.yml

1
2
3
4
5
6
server:
refresh: ${random.long}
key: refresh-test

config:
uuid: ${random.uuid}

读取配置的Bean,两种获取方式分别如下

1
2
3
4
5
6
@Component
@ConfigurationProperties(prefix = "server")
public class ServerConfig {
private String key;
private Long refresh;
}

开启刷新@Value的注解方式,注意下面的@RefreshScoe注解,这个必须有,否则更新后的配置不会同步

1
2
3
4
5
6
@RefreshScope
@Component
public class ValueConfig {
@Value("${config.uuid}")
private String uuid;
}

测试 Controller 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RestController
public class DemoController {
@Autowired
private ContextRefresher contextRefresher;

@Autowired
private ServerConfig serverConfig;

@Autowired
private ValueConfig valueConfig;

@GetMapping(path = "/show")
public String show() {
JSONObject res = new JSONObject();
res.put("biz", JSONObject.toJSONString(serverConfig));
res.put("uuid", valueConfig.getUuid());
return res.toJSONString();
}

@GetMapping(path = "/refresh")
public String refresh() {
new Thread(() -> contextRefresher.refresh()).start();
return show();
}
}

实例演示

启动上面的应用,然后开启愉快的测试,调用refresh接口,发现每次的返回都不一样(因为配置文件使用了random随机生成),但是访问show接口时,每次返回的都是一样的,也就是说refresh接口中确实实现了配置的刷新。

说明

  • 使用ConfigurationProperties方式获取注解时,自动支持刷新配置
  • 使用@Value注解的方式,需要开启@RefreshScope注解(上面没有演示不开启这个注解的情况, 建议有兴趣的可以自己尝试一下)

配置变更监听

既然配置能刷新,那么如果希望获取配置变更的事件,然后做一些其他的事情,是否ok呢?

其实进入 ContextRefresher 的源码,看下refresh接口,就很明确了

1
2
3
4
5
6
7
8
9
10
11
public synchronized Set<String> refresh() {
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());
addConfigFilesToEnvironment();
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
// 注意这一行,抛出了一个变更事件
this.context.publishEvent(new EnvironmentChangeEvent(context, keys));
this.scope.refreshAll();
return keys;
}

从上面的源码中,借助spring的事件通知机制,很简单就可以知道该怎么做了

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;

@Configuration
public class EnvConfiguration{
@EventListener
public void envListener(EnvironmentChangeEvent event) {
System.out.println("conf change: " + event);
}
}

注意下控制台的输出即可。


20-SpringBoot外部配置文件热加载
https://janycode.github.io/2024/05/27/08_框架技术/04_SpringBoot/20-SpringBoot外部配置文件热加载/
作者
Jerry(姜源)
发布于
2024年5月27日
许可协议