首页 > 其他分享 >【SpringBoot实用小知识】SpringBoot在运行时更新配置信息

【SpringBoot实用小知识】SpringBoot在运行时更新配置信息

时间:2024-08-21 09:24:51浏览次数:14  
标签:return SpringBoot 更新 实用 org import null config public

SpringBoot运行时更改配置信息

前言

对于很多项目而言 都需要配置来进行一些服务的设定 如果每次更改配置文件并重启 那么有些太浪费时间 我们可不可以通过请求修改配置 并直接看到更改呢?
在SpringCloud中 我们可以使用各种配置中心来完成这件事情 但是对于大部分项目而言 我们都是在一个较小的体量下进行的 如果只有SpringBoot 在不引入配置中心的情况下 我们可以实现动态更新配置信息吗
答案是可以的 我们的实现方式将不依赖其他第三方库 而是只采用SpringBoot本身的特质以及数据库完成 来保证配置的实时性和持久性

准备

一个普通的SpringBoot项目即可
Maven依赖参考如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from  repository -->
    </parent>

    <groupId>org.example</groupId>
    <artifactId>im-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>

        <!--springboot web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!-- Maven -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <!--数据库驱动-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.6</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>



</project>

由于需要使用数据库 所以要引入MySql驱动以及MybatisPlus用来简化数据库操作

思路

对于大部分配置类而言 它实际上的作用就是提供一个类 用于保存配置信息的字段 所以我们只需要在需要修改的时候 将类的成员值以及数据库字段值行修改即可 至于数据库更改 由于是单条数据 性能开销几乎可以不计 同时为了适配多种配置 如(安全配置 连接配置等不同种类的 又有可能需要经常调整的配置)
我们要抽象出一个类用于进行流程固定操作 也就是所谓的模板方法模式

有了修改的思路 调用起来就很简单了 只需要在Controller中设置修改和查询的接口(前提是做好权限管理 小型项目可以使用AOP或者Spring Security框架)

编码

测试用数据库配置表

思路在上面已经分析过 那么现在不再废话 首先给出用于测试的数据库表

create table test_config
(
    id        int auto_increment
        primary key,
    value_one int          null,
    value_two double       null,
    value_str varchar(255) null
);

为什么需要设置id值呢 一是为了方便管理 二是为了不同的版本控制 如果之后需要多个版本的配置来回切换 则可以通过枚举id值来获取到对应的记录 需要哪个版本的 可以直接使用对应的id值来查询

基础配置抽象类

用来定义通用属性以及模板方法

package org.demo.im.config;

import com.baomidou.mybatisplus.annotation.TableField;
import org.demo.im.util.BeanUtil;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @description: 基础配置类
 * @date: 2024/6/18 11:43
 * @version: 1.0
 */
@Component
public abstract class BaseConfig {
    @TableField(exist = false)
    //标注此成员不是表字段
    private ReentrantLock lock=new ReentrantLock();

    public void setConfigValue(String key,Object value){
        if (lock.isLocked()) {
            return;
        }
        lock.lock();
        Field field=null;
        try{
            field = BeanUtil.getFieldByName(this.getClass(),key);
            if(field==null){
                throw new IllegalArgumentException("配置类中不存在此配置项");
            }
            field.setAccessible(true);
            final Class<?> type = field.getType();
            final Object trueValue = convertToWrapper(value.toString(), type);
            field.set(this,trueValue);
            setConfigDBValue(this);
        }catch (Exception e){
            throw new IllegalArgumentException("不合法的配置");
        }
        finally {
            if(field!=null) {
                field.setAccessible(false);
            }
            lock.unlock();
        }
    }
    //转换类型 因为配置中可能有其他类型
    protected Object convertToWrapper(String value, Class<?> primitiveType) {
        if (primitiveType == Integer.class) {
            return Integer.valueOf(value);
        } else if (primitiveType == Double.class) {
            return Double.valueOf(value);
        }else if(primitiveType==String.class){
            return value;
        }
        // 其他类型可以添加到这里...
        throw new IllegalArgumentException("不支持的数据类型: " + primitiveType);
    }

    protected abstract void setConfigDBValue(Object instance) throws RuntimeException;
}

这里的锁是为了防止同一个配置被并发修改 而 setConfigValue 是提供给外界的调用用来修改的方法 setConfigDBValue 是子类需要实现的更新数据库表的方法
而BeanUtil是自己定义的类 完成获取指定类的成员属性的操作

package org.demo.im.util;

import org.springframework.stereotype.Service;

import java.lang.reflect.Field;

/**
 * @description: 操作bean的工具类
 * @date: 2024/8/18 11:40
 * @version: 1.0
 */

public class BeanUtil {
    public static Field getFieldByName(Class<?> clazz,String name){
        try {
            return clazz.getDeclaredField(name);
        } catch (NoSuchFieldException e) {
            return null;
        }
    }
}

测试配置实现类

首先我们需要定义数据库表的结构

package org.demo.im.config;


import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.demo.im.config.po.TestConfigMapper;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.Serializable;

/**
 * @description: 测试修改配置
 * @date: 2024/8/17 23:29
 * @version: 1.0
 */

@Component
@TableName(value ="test_config")
public class TestConfig extends BaseConfig implements Serializable {

    private Integer id;
    private Integer valueOne;
    private Double valueTwo;
    private String valueStr;


    public Integer getValueOne() {
        return valueOne;
    }

    public void setValueOne(Integer valueOne) {
        this.valueOne = valueOne;
    }

    public Double getValueTwo() {
        return valueTwo;
    }

    public void setValueTwo(Double valueTwo) {
        this.valueTwo = valueTwo;
    }

    public String getValueStr() {
        return valueStr;
    }

    public void setValueStr(String valueStr) {
        this.valueStr = valueStr;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        TestConfig other = (TestConfig) that;
        return (this.getValueOne() == null ? other.getValueOne() == null : this.getValueOne().equals(other.getValueOne()))
                && (this.getValueTwo() == null ? other.getValueTwo() == null : this.getValueTwo().equals(other.getValueTwo()))
                && (this.getValueStr() == null ? other.getValueStr() == null : this.getValueStr().equals(other.getValueStr()));
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getValueOne() == null) ? 0 : getValueOne().hashCode());
        result = prime * result + ((getValueTwo() == null) ? 0 : getValueTwo().hashCode());
        result = prime * result + ((getValueStr() == null) ? 0 : getValueStr().hashCode());
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", valueOne=").append(valueOne);
        sb.append(", valueTwo=").append(valueTwo);
        sb.append(", valueStr=").append(valueStr);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}

然后我们需要定义MybatisPlus用于操作数据库的Mapper接口

package org.demo.im.config;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.demo.im.config.TestConfig;

/**
 * @description: mapper层
 * @date: 2024/8/20 13:03
 * @version: 1.0
 */
@Mapper
public interface TestConfigMapper extends BaseMapper<TestConfig> {
}

然后完善我们的实现类

package org.demo.im.config;


import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.demo.im.config.po.TestConfigMapper;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.Serializable;

/**
 * @description: 测试修改配置
 * @date: 2024/8/17 23:29
 * @version: 1.0
 */

@Component
@TableName(value ="test_config")
public class TestConfig extends BaseConfig implements Serializable {

    private Integer id;
    private Integer valueOne;
    private Double valueTwo;
    private String valueStr;


    @Resource
    @TableField(exist = false)
    //非数据库字段
    private TestConfigMapper testConfigMapper;

    @Override
    protected void setConfigDBValue(Object instance) throws RuntimeException {
        //进行数据库读写操作
        if(instance instanceof TestConfig) {
            TestConfig config=(TestConfig)instance;
            testConfigMapper.update(config,new LambdaUpdateWrapper<TestConfig>()
                    .set(!ObjectUtils.isEmpty(config.getValueOne()),TestConfig::getValueOne,config.getValueOne())
                    .set(!ObjectUtils.isEmpty(config.getValueTwo()),TestConfig::getValueTwo,config.getValueTwo())
                    .set(!ObjectUtils.isEmpty(config.getValueStr()),TestConfig::getValueStr,config.getValueStr()));
        }else{
            throw new RuntimeException("无法同步到数据库 请检查传递的配置数据格式");
        }
    }

    @PostConstruct
    //在启动时初始化
    public void configInject(){
        //可以根据ID选择或者设置版本
        final TestConfig config = testConfigMapper.selectOne(new LambdaQueryWrapper<TestConfig>()
                .eq(TestConfig::getId,1));
        if(config==null){
            throw new RuntimeException("重要配置加载失败: testConfig");
        }
        this.valueOne=config.getValueOne();
        this.valueTwo=config.getValueTwo();
        this.valueStr=config.getValueStr();
    }


    public Integer getValueOne() {
        return valueOne;
    }

    public void setValueOne(Integer valueOne) {
        this.valueOne = valueOne;
    }

    public Double getValueTwo() {
        return valueTwo;
    }

    public void setValueTwo(Double valueTwo) {
        this.valueTwo = valueTwo;
    }

    public String getValueStr() {
        return valueStr;
    }

    public void setValueStr(String valueStr) {
        this.valueStr = valueStr;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        TestConfig other = (TestConfig) that;
        return (this.getValueOne() == null ? other.getValueOne() == null : this.getValueOne().equals(other.getValueOne()))
                && (this.getValueTwo() == null ? other.getValueTwo() == null : this.getValueTwo().equals(other.getValueTwo()))
                && (this.getValueStr() == null ? other.getValueStr() == null : this.getValueStr().equals(other.getValueStr()));
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getValueOne() == null) ? 0 : getValueOne().hashCode());
        result = prime * result + ((getValueTwo() == null) ? 0 : getValueTwo().hashCode());
        result = prime * result + ((getValueStr() == null) ? 0 : getValueStr().hashCode());
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", valueOne=").append(valueOne);
        sb.append(", valueTwo=").append(valueTwo);
        sb.append(", valueStr=").append(valueStr);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}

定义Controller接收

为了验证配置是否对不同组件生效 我们将查询和更改放在两个Controller中

package org.demo.im.controller;

import org.demo.im.config.TestConfig;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @description: 配置检查控制器
 * @date: 2024/8/18 0:12
 * @version: 1.0
 */
@RestController
@RequestMapping("/fuys/config")
public class ConfigSelectController {

    @Resource
    private TestConfig testConfig;

    @GetMapping("/select")
    public String selectConfig(){
        return testConfig.toString();
    }
}

用于修改的Controller如下

package org.demo.im.controller;

import org.demo.im.config.TestConfig;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @description: 配置测试控制器
 * @date: 2024/8/17 23:39
 * @version: 1.0
 */
@RestController
@RequestMapping("/fuys/config")
public class ConfigTestController {

    @Resource
    private TestConfig testConfig;

    @GetMapping("/update")
    public String updateConfig(String key,String value){
        System.out.println(value+" "+key);
        testConfig.setConfigValue(key,value);
        return "ok";
    }
}

测试

在测试之前我们需要在数据库中为配置表赋值 这个就随便赋值即可
现在我们启动SpringBoot项目 并通过PostMan来进行测试

在这里插入图片描述
在这里插入图片描述

可以看到数据是一样的 有些不需要的可以在ToString方法中去掉 或是优化接口 通过key查询 这也需要反射 获取到Field之后利用get方法即可获取值 如果不明白 可以评论或私信 我会修改文章补充

现在我们尝试修改
在这里插入图片描述
发现返回ok之后 再次尝试查询
在这里插入图片描述
数据库中数据也更改了
在这里插入图片描述

最后

经过文章的操作之后 我们就可以完成对于SpringBoot进行动态更新配置 可以看到测试中 配置的更新既完成了即时更新 又做到了持久性 还可以进行简单的版本管理 并且只依赖数据库和SpringBoot本身的初始化拓展注解完成 依赖性弱
最后 如果觉得文章对你有帮助 点个赞和关注吧 我会持续更新后端的实用小知识 以及 前端遇到的问题还有新奇的样式~

标签:return,SpringBoot,更新,实用,org,import,null,config,public
From: https://blog.csdn.net/m0_73918768/article/details/141288895

相关文章

  • springboot自动配置原理-面试题
    网络上看很多文章并没什么用,重点没说到,不知道从那里入手讲,刷到的直接按照下面这个,背出来就行了1、当启动springboot应用程序的时候,会先创建SpringApplication的对象,在对象的构造方法中会进行某些参数的初始化工作,最主要的是判断当前应用程序的类型以及初始化器和监听器,在这个......
  • 基于springboot万里学院摄影社团管理系统(源码+文档+调试+讲解)
    收藏关注不迷路!!......
  • openssh版本更新与说明 openssl版本更新与说明
    openssh版本截止到当前,官网最新的openssh稳定版本为 OpenSSH9.8/9.8p1 (2024-07-01)OpenSSH:ReleaseNotes基本主版本官方合作的镜像网站列表:OpenSSH:Mirrors可移植版官方合作的镜像网站列表:OpenSSH:PortableRelease版本区别OpenSSH9.8/9.8p1这是官方原文说明:Op......
  • springboot怎么配置多个yml文件
    目录方式一:多个yml文件方式二:单个yml文件方式三:在pom.xml中指定环境配置掌握方式一就够了,方式二、三可以不看以下三种方式都可以实现多环境的配置。在application.yml主配置文件中做项目通用的配置,在其他配置文件中做不同环境下的配置,以避免重复配置的情况。方式......
  • 【实用】【一眼就会】【直接可用】文件上传 附件上传 前后端分离 分布式 多文件上传
    思路:1、先保存主要信息,存到数据库。2、查询这条数据的id、uid3、上传附件功能:根据id、uid、文件。请求:附件API接口。4、后端接口中:先判断登录状态,5、创建对应的文件夹并存入文件,文件夹名以id名命名。6、把附件名重命名,以uuid命名。7、把所有的文件路径放到集合里,传入id......
  • 【2025毕设热门选题】《基于SpringBoot+Vue的校园资产管理系统》功能规划和开题报告
    博主介绍:8年资深码农、211小硕,全网10万+粉丝。文科生转码,所以非常懂小白学习历程。java领域优质创作者,擅长小白基础课程教学和项目讲解辅导。专注于Java技术领域和大学生毕业项目实战讲解已经5年,服务10000+小白客户。技术范围:自己手撸SpringBoot、Vue、javaweb网站、小程......
  • webpack 热更新实现原理
    修改entry配置首先通过启动webpack-dev-server会修改webpack.config.js的entry配置,新增两个入口文件:webpack-dev-server/client/index.jswebpack/hot/dev-server.jswebpack-dev-server/client/index.js包含的是客户端向服务端通信的相关代码。webpack/hot/dev-s......
  • SpringBoot Bean工具类 普通类环境中获取Bean
    通过实现BeanFactoryPostProcessor和ApplicationContextAware接口,可以在Spring容器启动时注入BeanFactory和ApplicationContext。importorg.springframework.aop.framework.AopContext;importorg.springframework.beans.BeansException;importorg.springframework.bean......
  • 【工具使用】【SpringBoot】【P6spy】P6spy 的使用
    1 前言今儿在看 HikariCP数据库连接池实战我主要是想看下,连接的管理、连接的获取及释放。但是看到第五章的时候,书中提到P6spy,说是能很容易监控到JDBC中执行的SQL语句。那我们平时SpringBoot微服务对数据的操作,不管是JDBCTemplate、还是Mybatis、Hibernate最后的落点......
  • 2024年华为OD目录,D卷&C卷,E卷即将更新!
    序言  本专栏收录的华为OD题目都会持续优化并且持续更新最新题目,一次购买,终生享受。2024年,华为OD机试已经启用了D卷,目前D卷和C卷的题目是一样的。我身边有很多同学通过本专栏已经成功上岸华为的OD员工,有同学成功转正为华为正式员工。根据内部消息,华为OD今年可能会使用E......