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同样也是一个成熟的可用方案,相比较来说,以数据库作为配置中心无需引入第三方也无需运行新服务,比较适合小型的项目。

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

相关推荐
invicinble8 分钟前
Data Validation数据校验机制与内容
spring boot
爱吃山竹的大肚肚36 分钟前
优化SQL:如何使用 EXPLAIN
java·数据库·spring boot·sql·spring
Kiyra1 小时前
Spring Boot Starter 自定义开发:封装中间件配置
spring boot·redis·后端·缓存·中间件·性能优化·rocketmq
码界奇点1 小时前
基于Spring Boot和微信小程序的小程序商城系统设计与实现
spring boot·微信小程序·小程序·毕业设计·源代码管理
+VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue英语学习系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
伯明翰java2 小时前
【无标题】springboot项目yml中使用中文注释报错的解决方法
java·spring boot·后端
码界奇点2 小时前
基于Spring Boot和Vue.js的视频点播管理系统设计与实现
java·vue.js·spring boot·后端·spring·毕业设计·源代码管理
廋到被风吹走2 小时前
【Spring】Spring Boot详细介绍
java·spring boot·spring
czlczl200209252 小时前
基于 Spring Boot 权限管理 RBAC 模型
前端·javascript·spring boot
计算机毕设指导62 小时前
基于微信小程序的智慧社区娱乐服务管理系统【源码文末联系】
java·spring boot·微信小程序·小程序·tomcat·maven·娱乐