SpringBoot统一属性配置,以数据库作为配置中心

引言

最近工作中常常感到后端 SpringBoot 项目里的属性配置十分混乱,在 SpringBoot 属性配置文件,数据库表,以及各个类中的属性字段都存在。因此,为了统一 SpringBoot 属性配置,尝试着以数据库作为统一的配置中心。

本文将从框架中属性配置存在的问题出发,分析并设计数据库表,之后改造属性的数据注入方式,最后在项目运行中完成验证可行性。

杂乱的属性配置

由于后台系统框架在设计之初没有对属性配置这一块做好统一规范,导致不同的人在着手不同业务的时候都用了各异的实现方式。随着涉及的业务越来越多,框架中属性配置这一块的问题也开始令人头大,配置臃肿的问题也终于到了该解决的时候。

首先概览一下目前 SpringBoot 项目中属性配置的存在情况,大致有三种形式:SpringBoot 属性配置文件、数据库配置表以及硬编码属性字段。

SpringBoot 属性配置文件

这类属性是配置在 SpringBoot 属性配置文件中的,部分配置还是十分敏感的数据,而且明文编写。

properties 复制代码
# 阿里云
aliyun.accessKeyId=LT***uao
aliyun.accessKeySecret=0Qhe***byr1f

数据库配置表

这类属性是读取数据库数据,之后加载到缓存或直接使用的。

硬编码属性字段

这类属性就是硬编码的常量,同样存在许多敏感数据。

java 复制代码
public class WechatConfig {

    public static final class MINI {
        public static final String APP_ID = "wx2***dd5";
        public static final String APP_SECRET = "38f***9c74";
    }
}

配置分离方案

配置分离就是将属性配置与项目工程代码分开,这样不仅可以提高程序的健壮性和可维护性,还能使得属性配置更容易管理,做到不修改代码或重启程序的情况下进行配置更改。

当然,并不是所有的属性配置都需要配置分离,还需要先将它们归类再进一步分析。

属性配置归类

首先分析框架与项目中使用的配置项,并规范为三类:

  • 第一种是 Spring 配置,比如Springserver.protMybatismybatis.mapper-locations,即属于框架层面的,这种规定要配置在application[-ENV].properties
  • 第二种是服务配置,比如定义的日志配置logback-spring.xml及配置logging.path,规定按原本方式单独配置;
  • 最后是第三方平台配置,比如阿里云OSS微信小程序,规定使用数据库作为配置中心。

第一和第二种配置方式基本无需改动,因此不考虑对其实现配置分离;本次着重于第三方平台相关的配置,并统一以数据库作为配置中心。

数据表设计

在数据库表的设计中,一个属性配置最少由两个字段组成,也就是构成键值对的字段;同时我们可以设计一个分组字段,便于数据的分类和检索;最后,考虑到项目之后的扩展,可以考虑设计一个命名空间字段,用于配置环境区分等。

  • 字段data_keydata_value作为单项配置的键值对,其含义等同于Spring中的properties
  • 字段data_group作为配置项的分组,例如对阿里云 OSS 的组名为aliyun.oss
  • 字段namespace为命令空间的含义,可以作为区分环境用,或者分租户。

数据表结构如下:

sql 复制代码
CREATE TABLE `admin_data_config` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增Id',
  `create_time` bigint(20) DEFAULT '0' COMMENT '创建时间',
  `create_user` bigint(20) DEFAULT '0' COMMENT '创建者',
  `update_time` bigint(20) DEFAULT '0' COMMENT '更新时间',
  `update_user` bigint(20) DEFAULT '0' COMMENT '更新者',
  `is_del` tinyint(4) DEFAULT '0' COMMENT '是否删除:1.是 0.否',
  `data_key` varchar(100) DEFAULT '' COMMENT '键',
  `data_value` varchar(100) DEFAULT '' COMMENT '值',
  `description` varchar(50) DEFAULT '' COMMENT '描述',
  `data_group` varchar(50) DEFAULT 'default_group' COMMENT '分组',
  `namespace` varchar(50) DEFAULT 'public' COMMENT '命名空间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='配置中心';

配置中心表数据示例:

编码实现

核心配置类

核心配置类中使用@PostConstruct注解的init读取数据库数据配置,

将读取到的数据配置作为MapPropertySource类型的数据源,

之后将MapPropertySource放置到MutablePropertySources首位 (addFirst),作为最高优先级配置。

java 复制代码
@Configuration
public class DBPropertyDataConfig {

    @Resource
    private ConfigurableEnvironment configurableEnvironment;

    @Resource
    private AdminDataConfigMapper adminDataConfigMapper;

    @PostConstruct
    public void init() {

        MutablePropertySources mutablePropertySources = configurableEnvironment.getPropertySources();
        List<AdminDataConfig> dataConfigList = adminDataConfigMapper.list(null);
        if (dataConfigList == null || dataConfigList.size() == 0) {
            return;
        }
        Map<String, Object> propertyMap = dataConfigList.stream()
                .collect(Collectors.toMap(AdminDataConfig::getDataKey, AdminDataConfig::getDataValue));
        MapPropertySource mapPropertySource = new MapPropertySource("db_data_config", propertyMap);
        mutablePropertySources.addFirst(mapPropertySource);

    }
}

数据注入

在应用新的数据库属性配置前还需要对原先的配置类改造,将字段或setter方法添加@Value注解使配置注入;同样地,对于新的属性配置则需要编写新的配置类。

以前面举例的WechatConfig配置类为例进行改造:

java 复制代码
@Configuration
public class WechatConfig {

    public static final class MINI {
        public static String APP_ID;
        public static String APP_SECRET;
    }
    
    @Value("${wechat.mini.app-id}")
    public void setAppId(String appId) {
        MINI.APP_ID = appId;
    }
    @Value("${wechat.mini.app-secret}")
    public void setAppSecret(String appSecret) {
        MINI.APP_SECRET = appSecret;
    }
}

注意事项

配置类的注意事项:

  • 成员变量不可使用final声明
  • 使用Congfiguration将类作为Spring Bean
  • static声明的变量注入需要在setter方法上注解
  • 不能在内部类执行注入

关于Spring Bean的加载顺序的说明:

  • 根据Spring Bean的加载顺序,要保证被注入Bean之前已加载DBPropertyDataConfig;例如,BlackBoxFacade会先于DBPropertyDataConfig加载,则需要@DependsOn指定其依赖关系。如下:
java 复制代码
@Service
@DependsOn("DBPropertyDataConfig")
public class BlackBoxFacade {
    @Value("${black-box.company}")
    public String SYS_COMPANY;
}

测试验证

数据准备

准备数据库数据:

sql 复制代码
INSERT INTO `admin_data_config`
(`data_key`, `data_value`, `description`, `data_group`, `namespace`)
VALUES
('wechat.mini.app-id', 'wx2***', '微信小程序 appid', 'wechat.mini', 'public'),
('wechat.mini.app-secret', '38f***', '微信小程序 app secret', 'wechat.mini', 'public');

接口测试

断点观测

  • 源代码设置断点:
  • 断点数据观测:

总结与扩展

至此,以数据库作为配置中心的实现就已完成了。这个过程中整个的代码量其实只有核心的数据库配置读取这一点,更多的工作量还是在配置类的改造上。在测试验证通过之后,这个方案已逐渐应用在了线上的所有项目。

当然,以数据库作为配置中心只是一个选择,nacos同样也是一个成熟的可用方案,相比较来说,以数据库作为配置中心无需引入第三方也无需运行新服务,比较适合小型的项目。

最后,以数据库作为配置中心只是统一属性配置改造中的一小环,后面将会继续探索如何动态刷新属性配置,让整个属性配置更加灵活方便。

相关推荐
goTsHgo21 分钟前
在 Spring Boot 的 MVC 框架中 路径匹配的实现 详解
spring boot·后端·mvc
钱多多_qdd31 分钟前
spring cache源码解析(四)——从@EnableCaching开始来阅读源码
java·spring boot·spring
飞的肖41 分钟前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
Q_192849990643 分钟前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
gb42152872 小时前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭11 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
AskHarries13 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion14 小时前
Springboot的创建方式
java·spring boot·后端
Yvemil715 小时前
《开启微服务之旅:Spring Boot Web开发举例》(一)
前端·spring boot·微服务
星河梦瑾16 小时前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全