简介
无论 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();
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" +
"}");
assertEquals("BAR22", config.getStr("FOO.STR"));
根据不同配置类型,有下面获取方法。
public String getStr(String key);
public boolean getBol(String key);
public int getInt(String key);
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;
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;
public EasyConfig(String filePath) {
super();
this.filePath = filePath;
}
public EasyConfig() {
super();
filePath = Resources.getResourcesFromClasspath("config.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.");
}
public void save(String jsonStr) {
FileHelper.saveText(filePath, jsonStr);
load();
}
private boolean loaded;
@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;
}
public String getStr(String key) {
return getAny(key, null, String.class);
}
public boolean getBol(String key) {
return getAny(key, false, boolean.class);
}
public int getInt(String key) {
return getAny(key, 0, int.class);
}
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 结构好(一棵“树”),所以得把这个树“扁平化”处理。具体就是这个函数,原理都是比较基础的数据结构知识。
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 修改器,下一期我们再讲。
|