最近工作中常常感到后端 SpringBoot 项目里的属性配置十分混乱,在 SpringBoot 属性配置文件,数据库表,以及各个类中的属性字段都存在。因此,为了统一 SpringBoot 属性配置,尝试着以数据库作为统一的配置中心。
本文将从框架中属性配置存在的问题出发,分析并设计数据库表,之后改造属性的数据注入方式,最后在项目运行中完成验证可行性。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
-
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
-
视频教程:https://doc.iocoder.cn/video/
由于后台系统框架在设计之初没有对属性配置这一块做好统一规范,导致不同的人在着手不同业务的时候都用了各异的实现方式。随着涉及的业务越来越多,框架中属性配置这一块的问题也开始令人头大,配置臃肿的问题也终于到了该解决的时候。
首先概览一下目前 SpringBoot 项目中属性配置的存在情况,大致有三种形式:
SpringBoot 属性配置文件
、
数据库配置表
以及
硬编码属性字段
。
这类属性是配置在 SpringBoot 属性配置文件中的,部分配置还是十分敏感的数据,而且明文编写。
> 基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
>
> * 项目地址:
> * 视频教程:
# 阿里云
aliyun.accessKeyId=LT***uao
aliyun.accessKeySecret=0Qhe***byr1f
这类属性是读取数据库数据,之后加载到缓存或直接使用的。
这类属性就是硬编码的常量,同样存在许多敏感数据。
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 配置,比如Spring的
server.prot
,Mybatis的
mybatis.mapper-locations
,即属于框架层面的,这种规定要配置在
application[-ENV].properties
;
-
第二种是服务配置,比如定义的日志配置
logback-spring.xml
及配置
logging.path
,规定按原本方式单独配置;
-
最后是第三方平台配置,比如阿里云OSS、微信小程序,规定使用数据库作为配置中心。
第一和第二种配置方式基本无需改动,因此不考虑对其实现配置分离;本次着重于第三方平台相关的配置,并统一以数据库作为配置中心。
在数据库表的设计中,一个属性配置最少由两个字段组成,也就是构成键值对的字段;同时我们可以设计一个分组字段,便于数据的分类和检索;最后,考虑到项目之后的扩展,可以考虑设计一个命名空间字段,用于配置环境区分等。
-
字段
data_key
和
data_value
作为单项配置的键值对,其含义等同于Spring中的properties;
-
字段
data_group
作为配置项的分组,例如对阿里云 OSS 的组名为
aliyun.oss
;
-
字段
namespace
为命令空间的含义,可以作为区分环境用,或者分租户。
数据表结构如下:
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),作为最高优先级配置。
@Configuration
public class DBPropertyDataConfig {
@Resource
private ConfigurableEnvironment configurableEnvironment;
@Resource
private AdminDataConfigMapper adminDataConfigMapper;
@PostConstruct
public void init() {
MutablePropertySources mutablePropertySources = configurableEnvironment.getPropertySources();
List dataConfigList = adminDataConfigMapper.list(null);
if (dataConfigList == null || dataConfigList.size() == 0) {
return;
}
Map propertyMap = dataConfigList.stream()
.collect(Collectors.toMap(AdminDataConfig::getDataKey, AdminDataConfig::getDataValue));
MapPropertySource mapPropertySource = new MapPropertySource("db_data_config", propertyMap);
mutablePropertySources.addFirst(mapPropertySource);
}
}
在应用新的数据库属性配置前还需要对原先的配置类改造,将字段或
setter
方法添加
@Value
注解使配置注入;同样地,对于新的属性配置则需要编写新的配置类。
以前面举例的
WechatConfig
配置类为例进行改造:
@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;
}
}
配置类的注意事项:
-
-
使用
Congfiguration
将类作为Spring Bean
-
static声明的变量注入需要在setter方法上注解
-
关于Spring Bean的加载顺序的说明:
根据Spring Bean的加载顺序,要保证被注入Bean之前已加载
DBPropertyDataConfig
;例如,
BlackBoxFacade
会先于
DBPropertyDataConfig
加载,则需要
@DependsOn
指定其依赖关系。如下: