AJ 组件库之 支持热加载的配置器 EasyConfig
Posted sp42a
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AJ 组件库之 支持热加载的配置器 EasyConfig相关的知识,希望对你有一定的参考价值。
简介
无论 xml 还是 yml 配置都归属 Spring 管理的,每次修改配置要重启服务器才能生效,于是思考怎么做一个不重启服务器的实现。实际上我之前都有探索过,参见我之前的博文:
平心而论,我之前的实现写得并不好,做的方法有些奇怪。好了,现在累积之前的坑和经验,重新再写,——这次写就是顺畅多了,一气呵成,代码也简单明了。
目前的实现就一个类,我们管他叫 EasyConfig
。
用法
Spring 注入这个组件,支持带构造器参数和不带的,参数就是配置 JSON 文件的磁盘路径。
<bean class="com.ajaxjs.util.config.EasyConfig" />
或:
<bean class="com.ajaxjs.util.config.EasyConfig" >
<constructor-arg index="0" value="config file path" />
</bean>
不带的构造器参数,默认是 classpath
下的 config.json
文件。
用法如下:
@Autowired
private EasyConfig config;
config.load(); // 加载配置
//@formatter:off
config.save("\\r\\n" + // 保存配置
" \\"clientShortName\\": \\"TEST\\",\\r\\n" +
" \\"FOO\\": \\r\\n" +
" \\"NUMBER\\": 1221,\\r\\n" +
" \\"STR\\": \\"BAR22\\",\\r\\n" +
" \\"BOOLEAN\\": true,\\r\\n" +
" \\"NULL\\": null,\\r\\n" +
" \\"ARRAY\\": [\\r\\n" +
" 1,\\r\\n" +
" \\"STR\\",\\r\\n" +
" null\\r\\n" +
" ]\\r\\n" +
" \\r\\n" +
"");
//@formatter:on
assertEquals("BAR22", config.getStr("FOO.STR")); // 读取
根据不同配置类型,有下面获取方法。
/**
* 读取配置并转换其为字符串类型。仅对扁平化后的配置有效,所以参数必须是扁平化的 aaa.bbb.ccc 格式。
*
* @param key 配置键值
* @return 配置内容
*/
public String getStr(String key);
/**
* 读取配置并转换其为 布尔 类型。仅对扁平化后的配置有效,所以参数必须是扁平化的 aaa.bbb.ccc 格式。
*
* @param key 配置键值
* @return 配置内容
*/
public boolean getBol(String key);
/**
* 读取配置并转换其为 int 类型。仅对扁平化后的配置有效,所以参数必须是扁平化的 aaa.bbb.ccc 格式。
*
* @param key 配置键值
* @return 配置内容
*/
public int getInt(String key);
/**
* 读取配置并转换其为 long 类型。仅对扁平化后的配置有效,所以参数必须是扁平化的 aaa.bbb.ccc 格式。
*
* @param key 配置键值
* @return 配置内容
*/
public long getLong(String key);
原理
先贴一下这个类的完整代码。
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import com.ajaxjs.util.io.FileHelper;
import com.ajaxjs.util.io.Resources;
import com.ajaxjs.util.logger.LogHelper;
import com.ajaxjs.util.map.JsonHelper;
import com.ajaxjs.util.map.ListMap;
/**
* 不需要重启服务器的配置
*
* @author sp42 frank@ajaxjs.com
*
*/
public class EasyConfig extends HashMap<String, Object>
private static final LogHelper LOGGER = LogHelper.getLog(EasyConfig.class);
private static final long serialVersionUID = 9099886055914666662L;
/**
* 配置文件路径
*/
private String filePath;
/**
* 创建一个配置器
*
* @param filePath 配置文件路径
*/
public EasyConfig(String filePath)
super();
this.filePath = filePath;
/**
* 创建一个配置器
*/
public EasyConfig()
super();
filePath = Resources.getResourcesFromClasspath("config.json");
/**
* 加载 JSON 配置
*/
public void load()
if (!new File(filePath).exists())
LOGGER.info("没有[0]项目配置文件", filePath);
return;
loaded = false;
String jsonStr = FileHelper.openAsText(filePath);
Map<String, Object> map = JsonHelper.parseMap(jsonStr);
clear();
putAll(ListMap.flatMap(map));
loaded = true;
LOGGER.infoGreen("加载[" + get("clientShortName") + "]项目配置成功!All config loaded.");
/**
* 保存配置
*
* @param jsonStr
*/
public void save(String jsonStr)
FileHelper.saveText(filePath, jsonStr);
load();
/**
* 是否加载成功
*/
private boolean loaded;
/**
* 内部的获取方法
*
* @param <T> 配置类型
* @param key 配置键值
* @param isNullValue 当配置为 null 时返回的值,相当于“默认值”
* @param vType 配置类型的引用
* @return 配置内容
*/
@SuppressWarnings("unchecked")
private <T> T getAny(String key, T isNullValue, Class<T> vType)
if (!loaded)
LOGGER.warning("配置系统未准备好");
return isNullValue;
Object v = get(key);
if (v == null)
LOGGER.warning("没发现[0]配置", key);
return isNullValue;
return (T) v;
/**
* 读取配置并转换其为字符串类型。仅对扁平化后的配置有效,所以参数必须是扁平化的 aaa.bbb.ccc 格式。
*
* @param key 配置键值
* @return 配置内容
*/
public String getStr(String key)
return getAny(key, null, String.class);
/**
* 读取配置并转换其为 布尔 类型。仅对扁平化后的配置有效,所以参数必须是扁平化的 aaa.bbb.ccc 格式。
*
* @param key 配置键值
* @return 配置内容
*/
public boolean getBol(String key)
return getAny(key, false, boolean.class);
/**
* 读取配置并转换其为 int 类型。仅对扁平化后的配置有效,所以参数必须是扁平化的 aaa.bbb.ccc 格式。
*
* @param key 配置键值
* @return 配置内容
*/
public int getInt(String key)
return getAny(key, 0, int.class);
/**
* 读取配置并转换其为 long 类型。仅对扁平化后的配置有效,所以参数必须是扁平化的 aaa.bbb.ccc 格式。
*
* @param key 配置键值
* @return 配置内容
*/
public long getLong(String key)
return getAny(key, 0L, long.class);
public String getFilePath()
return filePath;
public void setFilePath(String filePath)
this.filePath = filePath;
所为读取配置就是不断读取一个 Map 的 Key/Value。Key 为参数,Value 为返回的结果。这个类本身继承 HashMap,序列化为 JSON 保存,读取这个 JSON,是一个带结构的 Map(一棵“树”)。
在这个 Map 上,get("xx1").get("xx2").get("xx3")
?——那样太傻。或者 get("xx1.xx2.xx3")
?——那样不错,不过持久化也是那样吗?如:
“xx1.xx2.xx1”: 1,
“xx1.xx2.xx2”: 2,
“xx1.xx2.xx3”: 3,
Key 重复得厉害,一改就得多处修改,——还是原来带层次的 JSON 结构好(一棵“树”),所以得把这个树“扁平化”处理。具体就是这个函数,原理都是比较基础的数据结构知识。
/**
* 扁平化多层 map 为单层
*
* @param map 多层 Map
* @return 单层 Map
*/
public static Map<String, Object> flatMap(Map<String, Object> map)
final Stack<String> stack = new Stack<>();
final Map<String, Object> _map = new HashMap<>();
ListMapConfig config = new ListMapConfig();
config.newKey = key -> stack.add(key); // 进栈
config.exitKey = key -> stack.pop(); // 退栈
config.mapEntryHandler = (key, obj, currentMap, superMap, i) ->
if (obj == null || obj instanceof String || obj instanceof Number || obj instanceof Boolean)
StringBuilder sb = new StringBuilder();
for (String s : stack)
sb.append(s + ".");
_map.put(sb + key, obj);
return true;
;
traveler(map, config);
return _map;
这个类还调用了其他工具方法,很简单无非读取文件之类的,如果需要可以问我索取。
小结
这个配置器并不是要代替 Spring 的 properties/xml/yml 文件,其最大特点就是支持热加载,修改后程序马上生效。至于前端方面应该有个比较好的、通用的 JSON 修改器,下一期我们再讲。
以上是关于AJ 组件库之 支持热加载的配置器 EasyConfig的主要内容,如果未能解决你的问题,请参考以下文章