SpringBoot的配置如何动态刷新?

关注者
6
被浏览
41,948

4 个回答

前言

对于微服务而言配置本地化是个很大的鸡肋,不可能每次需要改个配置都要重新把服务重新启动一遍,因此最终的解决方案都是将配置外部化,托管在一个平台上达到不用重启服务即可 一次修改多处生效 的目的。

但是对于单体应用的Spring Boot项目而言,动态刷新显然是有点多余,反正就一个服务,改下重启不就行了?

然而在某些特殊的场景下还是必须用到动态刷新的,如下:

  1. 添加数据源 :对接某个第三方平台的时候,你不可能每次添加一个数据源都要重启下服务
  2. 固化的对接 :大量的固定对接方式,只是其中的某个固定的代码段不同,比如提供视图中的字段不同,接口服务中字段不同等情况。

当然以上列举的两种场景每个公司都有不同的解决方案,这里不做深究。

微服务下有哪几种主流的方案?

微服务下的动态配置中心有三种主流的方式,如下图:

上图中的三种配置中心方案可以说是现在企业中使用率最高的,分别是:

  1. Nacos :阿里巴巴的最近开源的项目,这个家伙很牛逼,一个干掉了 Eureka (停更)和 Config+Bus ,既能作为配置中心也能作为注册中心,并且有自己的独立的 管理平台,可以说是现在最主流的一种。
  2. Config+Bus :早期在用的微服务配置中心,可以依托 GitHub 管理微服务的配置文件,这种现在也是有不少企业在用,但是需要自己独立部署一个微服务,和 Nacos 相比逊色了不少。
  3. Apollo :携程开源项目Apollo,这个也是不少企业在用,陈某了解的不多,有兴趣的可以深入研究下。

针对Spring Boot 适用的几种方案?

其实上述三种都可以在Spring Boot项目中适配,但是作为单体应用有些重了,下面作者简单的介绍两种可用的方案。

Spring Boot+Nacos(不推荐)

不得不说阿里巴巴确实挺有野心,阿里要做的其实是一个微服务生态,Nacos不仅仅可以作为Spring Cloud的配置和注册中心,也适配了Dubbo、K8s,官方文档中对于如何适配都做了详细的介绍,作者 这里就不再详细介绍了,如下图:

当然Nacos对Spring、Spring Boot 项目同样适用。

如何使用呢?这里作者只提供下思路,不做过多的深究,这篇在作者下个专栏 Spring Cloud 进阶 会详细介绍:

  1. 下载对应版本的Nacos,启动项目,访问 http://localhost:8848 进入Nacos的管理界面;
  2. Spring Boot 项目引入Nacos的配置依赖 nacos-config-spring-boot-starter ,配置Nacos管理中心的地址。
  3. @NacosPropertySource @NacosValue 两个注解结合完成。
  • @NacosPropertySource :指定配置中心的 dataId ,和是否自动刷新
  • @NacosValue 替代 @Value 注解完成属性的自动装配
  1. 如果公司项目做了后台管理,则可以直接调用Nacos开放的API修改对应配置的值(替代了Nacos管理界面的手动操作),API的地址: nacos.io/zh-cn/docs/ope

此种方案虽说可以实现配置的动态刷新,但是还要集成Nacos,启动一个Nacos的服务,完全是有点大材小用了,实际项目中不推荐使用。

Spring Boot+Config+actuator(推荐)

此种方案实际使用的是Config配置中心,但是不像Nacos那般重,完全适用于单体应用的SpringBoot项目,只需要做小部分的更改即可达到效果。

方案一(不推荐)

  1. 添加Config的依赖,如下:
<!-- springCloud的依赖-->
<dependencyManagement>
    <dependencies>
        <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
    </dependencies>
</dependencyManagement>
<!-- config的依赖-->
<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
  <!-- actuator的依赖-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
  1. 配置文件中暴露Spring Boot的端点,如下:
management.endpoints.web.exposure.include=*
  1. 配置文件中新增三个属性配置:
config.version=22
config.app.name=dynamic-project
config.platform=mysql
  1. 结合 @RefreshScope 注解动态刷新,写个Controller,如下:
@RestController
//@RefreshScope该注解必须标注,否则无法完成动态更新
@RefreshScope
public class DynamicConfigController {
    @Value("${config.version}")
    private String version;
    @Value("${config.app.name}")
    private String appName;
    @Value("${config.platform}")
    private String platform;
    @GetMapping("/show/version")
    public String test(){
        return "version="+version+"-appName="+appName+"-platform="+platform;
  1. 启动项目测试,浏览器访问 http://localhost:8080/show/version ,返回信息如下图:
  1. 修改 target 目录下的配置文件,如下:
config.version=33
config.app.name=dynamic-project
config.platform=ORACLE
  1. POST请求 http://localhost:8080/actuator/refresh 接口,手动刷新下配置(必须,否则不能自动刷新)
  2. 浏览器再次输入 http://localhost:8080/show/version ,结果如下图:

可以看到,配置已经自动修改了,结束。

方案二(推荐)

看到了方案一觉得如何?是不是有点鸡肋了

第一个问题:为什么还要调用一次手动刷新呢?

第二个问题:只能手动的在配置文件中改吗?如果想在后台管理系统改怎么办?

想要解决上述两个问题还是要看下 Config 的源码,代码关键部分在 org.springframework.cloud.context.refresh.ContextRefresher#refresh() 方法中,如下图:

因此只需要在修改属性之后调用下 ContextRefresher#refresh() (异步,避免一直阻塞等待)方法即可。

为了方便测试,我们自己手动写一个refresh接口,如下:

@GetMapping("/show/refresh")
    public String refresh(){
        //修改配置文件中属性
        HashMap<String, Object> map = new HashMap<>();
        map.put("config.version",99);
        map.put("config.app.name","appName");
        map.put("config.platform","ORACLE");
        MapPropertySource propertySource=new MapPropertySource("dynamic",map);
        //将修改后的配置设置到environment中
        environment.getPropertySources().addFirst(propertySource);
        //异步调用refresh方法,避免阻塞一直等待无响应
        new Thread(() -> contextRefresher.refresh()).start();
        return "success";
上述代码中作者只是手动设置了配置文件中的值,实际项目中可以通过持久化的方式从数据库中读取配置刷新。

下面我们测试看看,启动项目,访问 http://localhost:8080/show/version ,发现是之前配置在 application.properties 中的值,如下图:

调用 refresh 接口: http://localhost:8080/show/refresh 重新设置属性值;

再次调用 http://localhost:8080/show/version 查看下配置是否修改了,如下图:

从上图可以发现,配置果然修改了,达到了动态刷新的效果。

总结

本文从微服务的配置中心介绍到Spring Boot 搭建简易的配置中心,详细介绍了几种可行性的方案,作者强力推荐最后一种方案,简化版的 Config ,完全适用于单体应用。

项目源码已经上传,有需要的回复关键词 008 获取。
Spring Boot的核心配置文件用于配置Spring Boot程序,文件名字必须以application开始。这个既是底层源码的强制要求,也是SpringBoot的一种代码规约,有助于在开发层面利于代码规范管理。

说明:以下内容接着i前面的SpringBootCase项目就行演示。

如何快速构建一个SpringBoot项目

SpringBoot项目结构及依赖技术栈

1、application. properties核心文件

格式:以键值对的方式进行配置,key=value 示例:name=xintu 我们修改application.properties配置文件。比如修改默认tomcat端口号及项目上下文件根。

设置内嵌Tomcat端口号

server.port=8888

设置项目上下文根路径,这个在请求的时候需要用到

server.servlet.context-path=/springbootcase

配置完毕之后,启动测试。

浏览器输入地址: http://localhost:8888/springbootcase/hello?name=lw , 页面验证结果如下。

2、 application.yml配置文件(推荐配置风格) yml 是一种 yaml 格式的配置文件,主要采用一定的空格、换行等格式排版进行配置。格式如下,

key: 空格+value, 表示一对键值对。

配置YNML时,需要注意以下4点:

1)以空格的缩进来控制层级关系。 2)只要是左对齐的一列数据,都是属于同一层级。 3)空格必须有。 4)属性和值对大小写敏感。
server:
  port: 8888 # 设置内嵌Tomcat端口号
  servlet:
    context-path: /SpringBootCase # 设置项目上下文根路径,这个在请求访问的时候需要用到

特点:与 application. properties 相比,yaml更能直观地被计算机识别,而且容易被人类阅读。 yaml 类似于 xml,但是语法比xml 简洁很多,值与前面的冒号配置项必须要有一个空格, yml 后缀也可以使用 yaml 后缀。当两种格式配置文件同时存在,使用的是 .properties 配置文件,为了演示yml,可以先将其改名,重新运行SpringbootApplication,查看启动的端口及上下文根。 我们在后面的学习过程中,均使用 .yml格式 。如果想改.properties形式也可以,按照自己喜欢的风格 或 团队约定即可。

3、SpringBoot多环境配置

在实际开发的过程中,我们的项目会经历很多的阶段,开发、测试、上线, 尤其时很大厂,在进行一些重要需求迭代时,还会包括预发、灰度等。每个阶段的配置会因应用所依赖的环境不同而不同,例如:数据库配置、缓存配置、依赖第三方配置等,那么这个时候为了方便配置在不同的环境之间切换,SpringBoot提供了多环境profile配置功能。 命名格式:application-环境标识.yml(或 .properties)。

下面我们为每个环境创建3个配置文件,分别命名为:application-dev.yml(开发环境)、application-dev.yml(测试环境)、application-dev.yml(生产环境)。

各配置文件内容如下,

application-dev.yml

#开发环境

server:
  port: 8881 # 设置内嵌Tomcat端口号
  servlet:
    context-path: /springbootcase1 # 设置项目上下文根路径,这个在请求访问的时候需要用到

application-test.yml

#测试环境
server:
  port: 8883 # 设置内嵌Tomcat端口号
  servlet:
    context-path: /springbootcase3 # 设置项目上下文根路径,这个在请求访问的时候需要用到

application-prod.yml

#生产环境
server:
  port: 8882 # 设置内嵌Tomcat端口号
  servlet:
    context-path: /springbootcase2 # 设置项目上下文根路径,这个在请求访问的时候需要用到

4)总配置文件application.yml进行环境的激活

spring:
  profiles:
      active: test #激活对应环境配置,以测试环境为例

说明:除此之外,还可以使用maven编译打包的方式配置profile。通常这种情况比较少见,需要根据各公司环境情况而定。

启动并测试验证如下,

浏览器输入: http://localhost:8883/springbootcase3/hello?name=lw, 结果如下,

4、SpringBoot自定义配置

在SpringBoot核心配置文件中,除以上使用内置的配置项之外,我们还可以在自定义配置,然后采用注解方式去读取配置。

1)@Value注解

步骤1:在核心配置文件applicatin.yml中,添加两个自定义配置项test.site和test.user。在IDEA中可以看到这两个属性不能被SpringBoot识别,背景是红色的。

步骤2:在SpringbootApplication中定义属性,并使用@Value注解或者自定义配置值,并对其方法进行测试 。

package com.xintu.demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class SpringbootApplication {
	@Value("${test.site}") //获取自定义属性test.site
	private String site;
	@Value("${test.user}")  //获取自定义属性test.user
	private String user;
	public static void main(String[] args) {
		SpringApplication.run(SpringbootApplication.class, args);
	@GetMapping("/hello")
	public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
		return String.format("欢迎 %s 来到<a href=\"http://www.35xintu.com\">35新途</a>!", name);
	@GetMapping("/value")
	public String testValue() { //测试@Value注解
		return String.format("欢迎 %s 来到<a href=\"http://www.35xintu.com\">%s</a>!" , user,site);
}

步骤3: 重新启动后,在浏览器中进行测试验证: http://localhost:8888/springbootcase/value。

2) @ConfigurationProperties

将整个文件映射成一个对象,用于自定义配置项比较多的情况。 步骤1:在 com.xintu.demo.config 包下创建 XinTuConfigInfo 类,并为该类加上@Component和@ConfigurationProperties注解,prefix可以不指定,如果不指定,那么会去配置文件中寻找与该类的属性名一致的配置,prefix的作用可以区分同名配置。

package com.xintu.demo.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "test")
public class XinTuConfigInfo {
    private String site;
    private String user;
    public String getSite() {
        return site;
    public void setSite(String site) {
        this.site = site;
    public String getUser() {
        return user;
    public void setUser(String user) {
        this.user = user;
}

步骤2: 在SpringbootApplication中注入XinTuConfigInfo 配置类。

package com.xintu.demo;
import com.xintu.demo.config.XinTuConfigInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class SpringbootApplication {
	@Autowired
	private XinTuConfigInfo configInfo; //测试@ConfigurationProperties
	@Value("${test.site}")
	private String site;
	@Value("${test.user}")
	private String user;
	public static void main(String[] args) {
		SpringApplication.run(SpringbootApplication.class, args);
	@GetMapping("/hello")
	public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
		return String.format("欢迎 %s 来到<a href=\"http://www.35xintu.com\">35新途</a>!", name);
	@GetMapping("/value")
	public String testValue() { //测试 @Value 注解
		return String.format("欢迎 %s 来到<a href=\"http://www.35xintu.com\">%s</a>!" , user,site);