【SpringBoot】 热部署 ContextRefresher.refresh() 自定义配置一键刷新 ~

前言

在实际项目中,有时候我们希望能够在不重启应用的情况下动态修改Spring Boot的配置,以便更好地应对变化的需求。本文将探讨如何通过从数据库动态加载配置,并提供一键刷新的机制来实现这一目标。

背景

最近的项目中,我遇到了一个需要动态调整应用配置的场景。在研究和实践中,我总结了一套简单而又有效的方法,可以通过数据库中的配置动态刷新Spring Boot应用的配置,而无需重启。

思路

我自己思路很简单,分为以下几个关键步骤:

  1. 获取应用上下文: 通过ConfigurableApplicationContext获取Spring Boot应用的上下文。
  2. 获取当前环境: 利用Environment对象获取当前应用的环境配置。
  3. 从数据库中获取最新配置: 编写数据库查询逻辑(或者可以有其他的更改途径,这里主要是博主的获取配置的途径),获取最新的配置信息。
  4. 替换当前环境的配置: 使用MutablePropertySources替换当前环境的配置。
  5. 刷新特定Bean: 调用ContextRefresherrefresh方法刷新指定的Bean。

思考

本来是想着直接看看能不能用 SpringBoot 的机制刷新Bean,真的 SpringBoot 上下文自己封装的 refresh 只能在加载的时候刷新一次,对于第二次的刷新有着严格的要求。本来说要探究一下源码的,算啦吧!SpringCloud 都已经有现成的热部署的工具了ContextRefresher,它真的为了分布式做了太多了,我哭死,所以我们来看看 SpringCloud 现成的ContextRefresher是如何实现热部署的吧。

参考环境

  • SpringBoot 2.5.8
  • SpringCloud 2021.0.1

具体步骤

1、Maven 依赖 和 配置

父项目

<!-- SpringCloud 微服务 -->

<spring-cloud.version>2021.0.1</spring-cloud.version>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

子项目

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-context</artifactId>
</dependency>

配置源加入,启动加载进Spring上下文。

management.endpoints.web.exposure.include="*"

2、 添加注解 @RefreshScope

给需要更改配置的 Bean 加上 @RefreshScope注解。

@RefreshScope
public class ConfigService{

    @Value("${grpc.client.xyregiserve.port}")
    private String regiservePort;

    @Value("${grpc.client.xyregiserve.url}")
    private String regiserveUrl;
}

3、 获取应用上下文

@Autowired
private ConfigurableApplicationContext applicationContext;

4、获取当前环境

/**
 * 获取当前环境
 */
ConfigurableEnvironment environment = applicationContext.getEnvironment();

5、从数据库中获取最新配置

ManagedChannelUtils.runWithManagedChannel(regiserveUrl, regiservePort, channel -> {
    try {
        PullConfigServiceGrpc.PullConfigServiceBlockingStub pullConfigServiceBlockingStub = PullConfigServiceGrpc.newBlockingStub(channel);

        /**
         * 从配置中心拉取配置
         */
        PullConfigResponse response = pullConfigServiceBlockingStub.getConfigByTag(PullConfigRequest
                .newBuilder()
                .setStr("user")
                .build());

        /**
         * 调用成功
         */
        if (response.getStatus() == 200) {

            /**
             * 获取到的配置 类型转化
             */
            Map<String, Object> newConfig = JSON.parseObject(response.getData(), new TypeReference<Map<String, Object>>() {
            });

        } else {

            throw new Exception("ServerConfig return code error");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
});

6、 替换当前环境的配置

/**
 * 替换或添加新的PropertySource
 */
if (propertySources.contains(PROPERTY_SOURCE_NAME)) {
    propertySources.replace(PROPERTY_SOURCE_NAME, newPropertySource);
} else {
    propertySources.addFirst(newPropertySource);
}

7、异步刷新

/**
 * 异步刷新
 */
Executors.newSingleThreadExecutor().execute(() -> contextRefresher.refresh());

8、刷新接口

在配置源更改配置之后,调用这个接口就可以刷新配置了。

@RestController
@RequestMapping("/refresh/config")
public class RefreshConfig {

    @Autowired
    private ConfigService configService;

    /**
     * 刷新配置
     * @return
     * @throws Exception
     */
    @GetMapping
    public AjaxResult refresh() throws Exception {
        configService.refreshConfig();
        return AjaxResult.success();
    }
}

完整代码

这个是博主的刷新配置的 ConfigService ,仅供参考。

package com.yanxi.user.web.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.yanxi.user.web.common.util.ManagedChannelUtils;
import com.yanxi.user.web.grpc.pullConfigService.PullConfigRequest;
import com.yanxi.user.web.grpc.pullConfigService.PullConfigResponse;
import com.yanxi.user.web.grpc.pullConfigService.PullConfigServiceGrpc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

@Service
@RefreshScope
public class ConfigService{

    @Value("${grpc.client.xyregiserve.port}")
    private String regiservePort;

    @Value("${grpc.client.xyregiserve.url}")
    private String regiserveUrl;

    @Autowired
    private ConfigurableApplicationContext applicationContext;

    @Autowired
    private ContextRefresher contextRefresher;

    private static final String PROPERTY_SOURCE_NAME = "databaseProperties";

    private static final Logger logger = Logger.getLogger(PullConfigLoader.class.getName());

    public Map<String, Object> refreshConfig() throws Exception {

        ManagedChannelUtils.runWithManagedChannel(regiserveUrl, regiservePort, channel -> {
            try {
                PullConfigServiceGrpc.PullConfigServiceBlockingStub pullConfigServiceBlockingStub = PullConfigServiceGrpc.newBlockingStub(channel);

                /**
                 * 拉取配置
                 */
                PullConfigResponse response = pullConfigServiceBlockingStub.getConfigByTag(PullConfigRequest
                        .newBuilder()
                        .setStr("user")
                        .build());

                /**
                 * 调用成功
                 */
                if (response.getStatus() == 200) {

                    logger.info("ServerConfig loading Success");

                    /**
                     * 类型转化
                     */
                    Map<String, Object> newConfig = JSON.parseObject(response.getData(), new TypeReference<Map<String, Object>>() {
                    });

                    /**
                     * 获取当前环境
                     */
                    ConfigurableEnvironment environment = applicationContext.getEnvironment();

                    /**
                     * 创建新的PropertySource
                     */
                    MapPropertySource newPropertySource = new MapPropertySource(PROPERTY_SOURCE_NAME, newConfig);

                    /**
                     * 获取PropertySourcess
                     */
                    MutablePropertySources propertySources = environment.getPropertySources();

                    /**
                     * 替换或添加新的PropertySource
                     */
                    if (propertySources.contains(PROPERTY_SOURCE_NAME)) {
                        propertySources.replace(PROPERTY_SOURCE_NAME, newPropertySource);
                    } else {
                        propertySources.addFirst(newPropertySource);
                    }

                    /**
                     * 异步刷新
                     */
                    Executors.newSingleThreadExecutor().execute(() -> contextRefresher.refresh());

                } else {

                    throw new Exception("ServerConfig return code error");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        return null;
    }
}

测试

做了一个测试类。

1700185850896.png

更改前配置源a的值为333。

image.png

接口调用测试类,a的值为也为333。

image.png

更改数据源a的值为222。

image.png

不用重启项目,调用配置刷新接口。

image.png

不用重启项目,调用测试类,a的值为也为222。 好的,更改过来了。

image.png

总结

一定要多思考,如果人永远待在舒适圈的话,人永远不会成长。共勉

觉得作者写的不错的,值得你们借鉴的话,就请点一个免费的赞吧!这个对我来说真的很重要。૮(˶ᵔ ᵕ ᵔ˶)ა

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/368168.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Deepin如何开启与配置SSH实现无公网ip远程连接

文章目录 前言1. 开启SSH服务2. Deppin安装Cpolar3. 配置ssh公网地址4. 公网远程SSH连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 前言 Deepin操作系统是一个基于Debian的Linux操作系统&#xff0c;专注于使用者对日常办公、学习、生活和娱乐的操作体验的极致&#xff0…

Bagging的随机森林;Boosting的AdaBoost和GBDT

集成学习应用实践 import numpy as np import os %matplotlib inline import matplotlib import matplotlib.pyplot as plt plt.rcParams[axes.labelsize] 14 plt.rcParams[xtick.labelsize] 12 plt.rcParams[ytick.labelsize] 12 import warnings warnings.filterwarnin…

java学习(面向对象基础)

一、继承(代码复用性&#xff09; 继承可以解决代码复用&#xff0c;让我们的编程更加靠近人类思维&#xff0c;当多个类存在相同的属性&#xff08;变量&#xff09;和方法时&#xff0c;可以从这些类中抽象出父类&#xff0c;在父类中定义这些相同的属性和方法&#xff0c;所…

实现无感刷新Token技术:.Net Web API与axios的完美结合

这是我之前分享在星球里面的课程&#xff0c;下面整理下&#xff0c;分享下这个无感刷新Token技术方案。 我们都知道Token是有设置有效期的&#xff0c;为了安全都不会设置过长的有效期&#xff1b;但设置有效期太短&#xff0c;又会导致经常需要重新登录。 这就需要无感刷新T…

Pyecharts炫酷散点图构建指南【第50篇—python:炫酷散点图】

文章目录 Pyecharts炫酷散点图构建指南引言安装Pyecharts基础散点图自定义散点图样式渐变散点图动态散点图高级标注散点图多系列散点图3D散点图时间轴散点图笛卡尔坐标系下的极坐标系散点图 总结&#xff1a; Pyecharts炫酷散点图构建指南 引言 在数据可视化领域&#xff0c;…

GPGPU面临的工程困境闲聊

作者&#xff1a;蒋志强 本人同意他人对我的文章引用&#xff0c;但请在引用时注明出处&#xff0c;谢谢&#xff0e;作者&#xff1a;蒋志强 0.前言 2007年作为GPGPU的工程界元年至今&#xff0c;已经发展了接近小二十年了。这个领域是如此的重要&#xff0c;几乎影响了工业…

MacBook Pro (15 英寸,2018) 本地体验运行 6B 大模型

接上篇 在 Mac 上加速 PyTorch 训练&#xff0c;准备完 MPS 环境之后&#xff0c;开始在本地体验 ChatGLM3-6B 模型。 一、下载本仓库&#xff1a; (base) markvivvMBP dev % git clone https://github.com/THUDM/ChatGLM3Cloning into ChatGLM3... remote: Enumerating obje…

[SWPUCTF 2021 新生赛]include

他让我们传入一个flag值 我们传入即可看到代码部分 传入一个php的伪类即可 得到经过Base64加密的flag&#xff0c;解密即可

jupyter notebook更改工作目录的2个细节

详细步骤参考知乎原文&#xff1a; 如何更改Jupyter Notebook的默认工作路径&#xff1f; - 知乎 (zhihu.com​​​​​​) 步骤4中需要删除 #符号和后面的空格&#xff01;一定要删除空格&#xff0c;否则会出现语法错误的报错 步骤5中&#xff0c;经过评论区提醒后&#xf…

酷开系统 | 酷开科技智慧AI带你领略神奇的世界

在这个科技日新月异的时代&#xff0c;AI已成为我们生活中不可或缺的一部分。它不仅改变了我们的生活方式&#xff0c;更让我们对未来充满期待。说起酷开系统中智慧AI的强大&#xff0c;着实让人叹为观止。无论是语音识别、数据整理还是语言处理&#xff0c;智慧AI都在不断地突…

【C++入门到精通】C++的IO流(输入输出流) [ C++入门 ]

阅读导航 引言一、C语言的输入与输出二、流是什么三、CIO流1. C标准IO流&#xff08;1&#xff09;istream&#xff08;2&#xff09;ostream&#xff08;3&#xff09;iostream&#xff08;4&#xff09;cin 和 cout 2. C文件IO流&#xff08;1&#xff09;ifstream&#xff0…

如何在Windows部署GoLand并通过SSH远程连接Linux服务器

文章目录 1. 安装配置GoLand2. 服务器开启SSH服务3. GoLand本地服务器远程连接测试4. 安装cpolar内网穿透远程访问服务器端4.1 服务器端安装cpolar4.2 创建远程连接公网地址 5. 使用固定TCP地址远程开发 本文主要介绍使用GoLand通过SSH远程连接服务器&#xff0c;并结合cpolar内…

1 初识JVM

JVM&#xff08;Java Virtual Machine&#xff09;&#xff0c;也就是 “Java虚拟机”。 对于第三点功能&#xff1a;即时编译 常见的JVM 默认安装在JDK中的虚拟机为HotSpot&#xff1a;可以用“java -version”进行查看

网络时间协议NTP工作模式

单播服务器/客户端模式 单播服务器/客户端模式运行在同步子网中层数较高层上。这种模式下,需要预先知道服务器的IP地址。 客户端:运行在客户端模式的主机(简称客户端)定期向服务器端发送报文,报文中的Mode字段设置为3(客户端模式)。当客户端接收到应答报文时,客户端会…

RHCE 综合项目-博客

目录 业务需求 一、准备工作 1、配置静态IP 2、修改主机名及hosts映射 3、开启防火墙 4、时间同步 5、配置免密ssh登录 二、环境搭建 1、Server-web端安装LAMP环境软件 2、Server-NFS-DNS端上传博客软件 3、Server-NFS-DNS端设置NFS共享 三、Server-web设置 1、挂…

新手从零开始学习数学建模论文写作(美赛论文临时抱佛脚篇)

本文记录于数学建模老哥视频的学习过程中。b站视频&#xff1a;http://【【零基础教程】老哥&#xff1a;数学建模算法、编程、写作和获奖指南全流程培训&#xff01;】https://www.bilibili.com/video/BV1kC4y1a7Ee?p50&vd_sourceff53a726c62f94eda5f615bd4a62c458 目录…

四、Redis之配置文件

redis配置文件的名称 redis.conf 通过命令 find / -name redis.confvim redis.conf通过 : set nu 设置行号: set nonu 取消行号/关键字 搜索关键字: set noh 取消高亮选择4.1 Units 配置大小单位&#xff0c;开头定义了一些基本的度量单位&#xff0c;只支持 bytes&#…

多线程有三个必须要保证的特性,才能正常运行(三个特性是:有序性,可见性,原子性)JMM的作用就是保证这三个特征

有序性的原因&#xff0c;和保证措施&#xff08;as-if-serial&#xff09; JVM执行代码时&#xff0c;可能会优化编译器和优化CPU的性能发挥&#xff0c;所以会进行对 代码顺序调整。当然&#xff0c;此顺序会保证as-if-serial&#xff08;也就是再怎么优化顺序&#xff0c;单…

Spring-mvc、Spring-boot中如何在调用同类方法时触发AOP

1. 问题描述 Spring-mvc和Spring-boot中aop可以实现代理的功能&#xff0c;我们可以借此实现事务和日志记录或者限流等多种操作。但是&#xff0c;如果你在一个方法中调用其同类下的其他方法的时候不会触发AOP。本文主要说明其原因及解决办法和实现原理。 2. 原因 AIOP的本质是…

网络编程套接字(3)

网络编程套接字 简单的TCP英译汉服务器地址转换函数字符串IP转整数IP整数IP转字符串IP关于inet_ntoa函数并发场景下的inet_ntoa函数绑定失败问题TCP协议通讯流程数据传输的过程数据交互四次挥手的过程端口连接 简单的TCP英译汉服务器 之前我们是以回调的方式处理任务的&#x…