本方法依赖于 JRE 自带的 JavaScript 引擎 Rhino,无须其他第三方 JAR 包。更多这方面的资料,参见《学习使用 Java 自带的 JS 引擎》和《使用自带的 Rhino 作为 Java 的 JSON 解析包》。
具体流程参见源码:http://code.taobao.org/p/bigfoot_v2/src/java/com/ajaxjs/framework/javascript/JSON_Saver.java。
读取配置文件
先大概说一说思路。首先配置文件以 *.json 格式保存在服务端磁盘上。要读取改配置文件的话,通过 java.io.File 包读取磁盘内容,然后形成接口,作为响应内容返回到客户。既然 Web 浏览器天然支持 JSON,这读取一过程我们借助 <script src="xxx.json"></script> 即可。得到 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,让浏览器通过 <script src="xxx.jsp"></script> 访问得到即可。另外这里我还加入了格式化函数(Formatter),让输出的 JSON 带有缩进,更方便调试和阅读。
<script type="text/framework/javascript" src="?getConfig"></script>如上例,我是设置当前 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 节点。
表单代码如下:
<form class="form-horizontal" method="post" action="?"> <input type="hidden" name="jsonFile" value="/META-INF/site_config.js" /> <input type="hidden" name="topVarName" value="bf_Config" /> <div class="control-group"> <label class="control-label">网站标题前缀</label> <div class="controls"> <input type="text" name="bf_Config.site.titlePrefix" class="ui-wizard-content" placeholder="请输入旧密码" errmsg="旧密码为必填项" requiredfield /> </div> </div> <div class="control-group"> <label class="control-label">搜索关键字</label> <div class="controls"> <textarea name="bf_Config.site.keywords" rows="10"></textarea> <span class="requiredfield">*</span> </div> </div> <div class="control-group"> <label class="control-label">网站描述</label> <div class="controls"> <textarea name="bf_Config.site.description" rows="10"></textarea> <span class="requiredfield">*</span> </div> </div> <div class="control-group"> <label class="control-label">底部版权<br />声明文字 </label> <div class="controls"> <textarea name="bf_Config.site.footCopyright" rows="10"></textarea> <span class="requiredfield">*</span> </div> </div> <div class="form-actions"> <button class="btn btn-success submitBtn">保存</button> </div> </form>
生成表单界面如下:
其中要注意两处隐藏域:
<input type="hidden" name="jsonFile" value="/META-INF/site_config.js" /> <input type="hidden" name="topVarName" value="bf_Config" />一个说明是哪个 JSON 文件要被保存的,一个是 JSON 的*节点名。
保存配置内容
点击“保存”按钮之后,表单提交如下数据到后台。
具体流程参见源码:http://code.taobao.org/p/bigfoot_v2/src/java/com/ajaxjs/framework/javascript/JSON_Saver.java
保存配置文件,一个是修改当前内存的配置信息,其次是将配置内存保存在服务端的磁盘上,所以说这里是两个主要的操作,因此也需要两个 JavaScript 运行时。一个是 BaseApplication.jsRuntime 静态类型的,常驻内存的,另外一个是用于当前请求的实例化的这么一个 JS 运行时,用于得到 JSON 序列化后的字符串。它们分别作用各不同。
以上逻辑都安排在 save(HttpServletRequest request) 方法:
private void save(HttpServletRequest request) throws Exception { Map<String, String> 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<String, String> hash = RequestSender.getClient_Data(request);,加载到内存中,然后对其修改(实际是覆盖过程,同时对两个 js runtime 皆有效),最后保存到文件中(Fso.write)。
/** * 写入内存,覆盖 * @param hash */ private void save(Map<String, String> 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 为一个,不知是否可行呢?