很久很久以前当微服务还没出现、配置中心还没出现、yaml配置文件也还没流行的时候,我们都习惯在项目里面写一个类似ResourceUtils
或者PropertiesUtil
的工具,无论在静态方法还是jsp代码都屡试不爽。如今Springcloud各种参数化配置、各种profile,Apollo、Nacos各种配置中心以及Properties、Yaml各种配置格式你的配置文件读取工具还好么?接下来我们讲解Mendmix
项目的ResourceUtils
工具类开启我们Mendmix代码解析系列课程
的篇章。
介绍
ResourceUtils贯穿了Mendmix
项目各个生命周期,在整个项目中被大量使用。目前支持Properties、Yaml配置文件解析、兼容Springcloud的profile配置方式以及无缝兼容Apollo、Nacos等各种各样的配置中心。同时还提供了诸多快速配置文件读取的方法,如:getListValue,getMapValue、getBeanValue等。
代码分析
加载过程
通过静态代码块加载本地配置文件
static {
loadLocalConfigs();
}
loadLocalConfigs这个方法首先尝试获取spring.profiles.active
的值,为了兼容通过--spring.profiles.active=prd
的方式指定spring.profiles.active
的值,Mendmix提供了一个应用启动类基类BaseApplicationStarter
,在这个基类里面会处理各种运行参数并把设置为系统变量。
为了兼容本地运行或打包运行提供了loadPropertiesFromFile
和loadPropertiesFromJarFile
两个读取配置的方法,通过这两个方法会把本地的所有.properties
及.yaml
文件找出来(你也拦不住两种格式的配置文件混用),并建立<文件后缀,[文件名称列表]>的映射关系如下:
{
".properties" : [ "/.../application-local.properties", "/.../application.properties" ]
}
接下来调用parseSameExtensionFiles
方法依次循环解析每个后缀名的配置文件,这个方法做了两件事情:加载所有不带profile的配置文件和找出profile的配置文件。为了确保profile的配置文件能覆盖默认配置,找出的profile的配置文件放在做好放入解析好的所有配置文件Properties里面。这样整个解析过程就完成了。
处理${}引用配置
直接贴代码吧,有点长,类似方法实际上Spring提供的有,仅仅是考虑这只是一个工具类,进来少依赖,所以就自己写了。
public static String replaceRefValue(Properties properties,String value ) {
if(!value.contains(PLACEHOLDER_PREFIX)){
return value;
}
String[] segments = value.split("\\$\\{");
String seg;
StringBuilder finalValue = new StringBuilder();
for (int i = 0; i < segments.length; i++) {
seg = StringUtils.trimToNull(segments[i]);
if(StringUtils.isBlank(seg))continue;
if(seg.contains(PLACEHOLDER_SUFFIX)){
String refKey = seg.substring(0, seg.indexOf(PLACEHOLDER_SUFFIX)).trim();
//其他非${}的占位符如:{{host}}
String withBraceString = null;
if(seg.contains("{")){
withBraceString = seg.substring(seg.indexOf(PLACEHOLDER_SUFFIX)+1);
}
//如果包含默认值,如:${host:127.0.0.1}
String defaultValue = null;
int defaultValSpliterIndex = refKey.indexOf(":");
if(defaultValSpliterIndex > 0){
defaultValue = refKey.substring(defaultValSpliterIndex + 1);
refKey = refKey.substring(0,defaultValSpliterIndex);
}
String refValue = System.getProperty(refKey);
if(StringUtils.isBlank(refValue))refValue = System.getenv(refKey);
if(StringUtils.isBlank(refValue))refValue = properties.getProperty(refKey);
if(StringUtils.isBlank(refValue)){
refValue = defaultValue;
}
if(StringUtils.isBlank(refValue)){
finalValue.append(PLACEHOLDER_PREFIX + refKey + PLACEHOLDER_SUFFIX);
}else{
finalValue.append(refValue);
}
if(withBraceString != null){
finalValue.append(withBraceString);
}else{
String[] segments2 = seg.split("\\}");
if(segments2.length == 2){
finalValue.append(segments2[1]);
}
}
}else{
finalValue.append(seg);
}
}
整合配置中心
考虑到各种各样的配置中心,所以我们不能与具体配置中心产品绑定。所以Mendmix从Spring加载的生命周期下手。在Environment
对象加载完成后对所有配置进行一次合并,代码如下:
private static void mergeEnvironmentProperties(){
MutablePropertySources propertySources = ((ConfigurableEnvironment)environment).getPropertySources();
int count;
for (PropertySource> source : propertySources) {
if(source.getName().startsWith("servlet") || source.getName().startsWith("system")){
continue;
}
if(source.getName().contains("applicationConfig: [classpath")) {
continue;
}
count = 0;
if (source instanceof EnumerablePropertySource) {
for (String name : ((EnumerablePropertySource>) source) .getPropertyNames()) {
Object value = source.getProperty(name);
if(value != null){
ResourceUtils.add(name, value.toString());
count++;
}
}
}
System.out.println(">>merge PropertySource:" + source.getName() + ",nums:" + count);
}
}
该类在com.mendmix.spring.helper.EnvironmentHelper
,这里要注意两点:
- 确保在应用bean初始化之前完成合并
- 要跳过本地配置合并,否则可能出现远程配置又本本地配置覆盖的情况。
用法举例
假如有一份这样的配置文件
whitelist.ips=10.1.1.10;10.1.1.100
#aliyun OSS
mendmix.cos.adapter.type=aliyun
mendmix.cos.adapter.accessKey=5tHzzxhTs45tbUrKgTHYxxxx
mendmix.cos.adapter.secretKey=aIDWMP2pbvFjML7tYAzfPXXXXXXX
mendmix.cos.adapter.regionName=cn-guangzhou
#feign代理
mendmix.loadbalancer.customize.mapping[mendmix-user-svc]=http://127.0.0.1:8081
mendmix.loadbalancer.customize.mapping[mendmix-order-svc]=http://127.0.0.1:8082
部分用法
//查询指定前缀的配置
Properties properties = ResourceUtils.getAllProperties("mendmix.cos.adapter");
//查询指定前缀并返回对象
CosConfig cosConfig = ResourceUtils.getBean("mendmix.cos.adapter", CosConfig.class);
//KV格式的配置
Map mappingValues = ResourceUtils.getMappingValues("mendmix.loadbalancer.customize.mapping");
//返回列表
List whitelistIps = ResourceUtils.getList(" whitelist.ips");
// 多个配置兼容
ResourceUtils.getAnyProperty("key1","key2");
关于Mendmix
Mendmix基于Apache 2.0开源协议,100%开源。定位是一站式分布式开发架构开源解决方案及云原生架构技术底座。Mendmix提供了数据库、缓存、消息中间件、分布式定时任务、安全框架、网关以及主流产商云服务快速集成能力。基于Mendmix可以不用关注技术细节快速搭建高并发高可用基于微服务的分布式架构。
项目地址:github,gitee