本方法依赖于 JRE 自带的 JavaScript 引擎 Rhino,无须其他第三方 JAR 包。更多这方面的资料,参见《学习使用 Java 自带的 JS 引擎》和《使用自带的 Rhino 作为 Java 的 JSON 解析包》。
读取配置文件
先大概说一说思路。首先配置文件以 *.json 格式保存在服务端磁盘上。要读取改配置文件的话,通过 java.io.File 包读取磁盘内容,然后形成接口,作为响应内容返回到客户。既然 Web 浏览器天然支持 JSON,这读取一过程我们借助 即可。得到 JSON 数据后,再通过 JavaScript 绑定到 HTML 表单上。
读取 JSON 文件内容很简单,我的代码如下:
String filePath = new RequestHelper(request).Mappath("/META-INF/site_config.js");
out.println(Fso.readFile(filePath));
RequestHelper.Mappath 和 Fso.readFile 都是我封装的函数,分别是把虚拟的相对路径还原为磁盘的绝对路径;Fso.readFile 顾名思义是读取文件内容。你可以将此写成 JSP 或 Servlet,让浏览器通过 访问得到即可。另外这里我还加入了格式化函数(Formatter),让输出的 JSON 带有缩进,更方便调试和阅读。
如上例,我是设置当前 JSP 得到 JSON 内容的,效果如下图。
注意我们把 JSON 显示到表单是通过下面简单的函数的:
// 数据绑定
// v1.00
function databing(data){
for(var i in data){
var el = document.querySelector('*[name="bf_Config.site.{0}"]'.format(i));
el.value = data[i];
}
}
databing(bf_Config.site);这里表单各个输入控件的 name 属性是有讲究的,命名方式都是以 JSON 的中 JSON Path 完整路径为依据,如这里的 bf_Config.site.{0}。一个控件项对应一个 JSON 节点。
表单代码如下:
网站标题前缀
placeholder="请输入旧密码" errmsg="旧密码为必填项" requiredfield />
搜索关键字
*
网站描述
*
底部版权
声明文字
*
保存
生成表单界面如下:
其中要注意两处隐藏域:
一个说明是哪个 JSON 文件要被保存的,一个是 JSON 的顶级节点名。
保存配置内容
点击“保存”按钮之后,表单提交如下数据到后台。
保存配置文件,一个是修改当前内存的配置信息,其次是将配置内存保存在服务端的磁盘上,所以说这里是两个主要的操作,因此也需要两个 JavaScript 运行时。一个是 BaseApplication.jsRuntime 静态类型的,常驻内存的,另外一个是用于当前请求的实例化的这么一个 JS 运行时,用于得到 JSON 序列化后的字符串。它们分别作用各不同。
以上逻辑都安排在 save(HttpServletRequest request) 方法:
private void save(HttpServletRequest request) throws Exception {
Map hash = RequestSender.getClient_Data(request);
String jsonFileFullPath = load(request, hash);
// 可以json.str() 的 变量名
String topVarName = hash.get("topVarName");
Util.isEmptyString(topVarName, "没有 topVarName 参数!");
hash.remove("topVarName");
saveRAM(hash);
String JSON_as_String = null;
try {
JSON_as_String = (String)eval("JSON.stringify(" + topVarName + ");");
} catch (ScriptException e) {
e.printStackTrace();
throw new Exception("更新配置失败,不能序列化配置!");
}
if(JSON_as_String != null){ // 持久化配置文件
String fileBody = topVarName + " = " + JSON_as_String + ";";
//System.out.println(fileBody);
//System.out.println("::::::::::::::::::2:"+jsonFileFullPath);
try {
Fso.writeFile(jsonFileFullPath, fileBody);
} catch (IOException e) {
e.printStackTrace();
throw new Exception("更新配置失败,不能保存配置!");
}
}
}
首先,我们需要把这些数据变为一个 Map:Map hash = RequestSender.getClient_Data(request);,加载到内存中,然后对其修改(实际是覆盖过程,同时对两个 js runtime 皆有效),最后保存到文件中(Fso.write)。
/**
* 写入内存,覆盖
* @param hash
*/
private void save(Map hash){
String jsCode = "";
for(String key : hash.keySet()){
jsCode = key + " = '" + hash.get(key) + "';"; // 全部保存为 String。TODO 支持其他类型
//System.out.println(jsCode);
try{
js.eval(jsCode); // 两个 js runtime
eval(jsCode);
}catch(ScriptException e) {
System.err.println("写入内存,覆盖失败!");
e.printStackTrace();
}
}
}
最后保存完毕,输出 JSON 结果:new ResponseHelper(response).outputJSON(request); 提示用户成功。应该说整个过程并不复杂,操作也足够直观。如果要说有什么地方没考虑到的,就是安全性了。实际使用中还需要注意权限,因为这是直接对服务端的文件进行写操作!
另外有一点可以优化的地方,那就是合并两个 js runtime 为一个,不知是否可行呢?