本文将逐一介绍以下工具:
上图是ftl文件解析的过程,输入ftl模板和对应java对象,经过freemarker.jar包解析后,得到输出文本;这个是我们在前后端分离前,运行java web服务执行的过程;那么,我们想脱离java web端逻辑来执行这个过程,该如何实现?关键点在这个java对象,下面给出两种实现:
通常情况下, 我们的请求与ftl的实际路径是没有关联的,我们需要在controller中定义url与ftl实际路径的关系,这样我们就需要维护这个关系,不利于抽象复用;由于我们是本地测试开发环境,不需要通过对应的url访问页面,直接通过实际的路径去访问,这样只需要一个controller统一处理,将url中路径的部分解析出来,处理对应的ftl即可;
*.ftl
请求,拦截到后解析url路径,找到对应的ftl模板文件让freemarker处理后返回给浏览器;
以tomcat server + ftl.jar为例, tomcat配置web.xml如下:将所有*.ftl交给com.cjx.ftl.Ftl这个类去处理,ftl.jar是自己写的一个包,里面的只实现了上面说的统一处理请求,解析路径的功能;
package com.cjx.ftl; import com.cjx.ftl.CjxBeanBeanWrapper; import freemarker.ext.servlet.HttpRequestHashModel; import freemarker.ext.servlet.HttpRequestParametersHashModel; import freemarker.ext.servlet.HttpSessionHashModel; import freemarker.ext.servlet.ServletContextHashModel; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException;
import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Locale; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class Ftl extends HttpServlet { private static final long serialVersionUID = 1L; private Configuration cfg = null; private static final String ATTR_APPLICATION_MODEL = ".freemarker.Application"; private static final String ATTR_REQUEST_MODEL = ".freemarker.Request"; private static final String ATTR_REQUEST_PARAMETERS_MODEL = ".freemarker.RequestParameters"; public static final String KEY_APPLICATION = "Application"; public static final String KEY_REQUEST_MODEL = "Request"; public static final String KEY_SESSION_MODEL = "Session"; public static final String KEY_REQUEST_PARAMETER_MODEL = "Parameters"; public Ftl() { this.cfg = new Configuration(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext servletContext = request.getServletContext(); response.setContentType("text/html; charset=UTF-8"); request.setCharacterEncoding("UTF-8"); String fPath = request.getServletContext().getRealPath(""); String fullPath = request.getServletPath(); if(!"".equals(fullPath) && fullPath.endsWith("ftl")) { HashMap root = new HashMap(); ServletContextHashModel servletContextModel = (ServletContextHashModel)servletContext.getAttribute(".freemarker.Application"); CjxBeanBeanWrapper wrapper = new CjxBeanBeanWrapper(false); if(servletContextModel == null) { servletContextModel = new ServletContextHashModel(this, wrapper); servletContext.setAttribute(".freemarker.Application", servletContextModel); } root.put("Application", servletContextModel); HttpRequestHashModel requestModel = (HttpRequestHashModel)request.getAttribute(".freemarker.Request"); if(requestModel == null || requestModel.getRequest() != request) { requestModel = new HttpRequestHashModel(request, response, wrapper); request.setAttribute(".freemarker.Request", requestModel); } root.put("Request", requestModel); HttpRequestParametersHashModel reqParametersModel = (HttpRequestParametersHashModel)request.getAttribute(".freemarker.RequestParameters"); if(reqParametersModel == null || requestModel.getRequest() != request) { reqParametersModel = new HttpRequestParametersHashModel(request); request.setAttribute(".freemarker.RequestParameters", reqParametersModel); } root.put("Parameters", reqParametersModel); HttpSession session = request.getSession(true); if(session != null) { root.put("Session", new HttpSessionHashModel(session, wrapper)); } root.put("request", request); root.put("response", response); root.put("session", request.getSession(true)); root.put("base", request.getContextPath()); this.cfg.setDirectoryForTemplateLoading(new File(fPath)); this.cfg.setAutoFlush(true); this.cfg.setDefaultEncoding("UTF-8"); this.cfg.setEncoding(Locale.CHINA, "UTF-8"); fullPath = fullPath.substring(1); Template t = this.cfg.getTemplate(fullPath, Locale.CHINA); try { t.process(root, response.getWriter()); } catch (TemplateException var17) { var17.printStackTrace(); } finally { response.getWriter().close(); } } } }
再次回到最开始的ftl模板解析流程图,脱离了java web环境,数据就不能存放在java对象中,既然前后端分离了,我们最希望它能维护在json文件中,但是json文件,如何才能解析替换ftl模板中的freemarker变量?接下来,就引出了Fmpp工具,它可以替我们完成这个工作;
fmpp sourcefile -C configfile
FMToll与Fmpp功能类似,实现不同,FMToll没有文档,如果感兴趣,可以研究下源码;
有了fmpp,我们就可以使用fmpp命令将ftl模板与json文件结合生成解析文件;但是在实际工程应用中,我们不可能手动对分布在各个目录下的ftl模板文件,执行解析命令;这时候就需要一些工具帮我们实现解析的自动化;
主要就是一个方法render;
var Freemarker = require('freemarker.js');
var fm = new Freemarker({
viewRoot: '/template',
options: {
/** for fmpp */
}
});
// Single template file
fm.render(tpl, dataObject, function(err, html, output) {
//...
});
上面代码的内部实现:根据参数和dataObject生成config文件,对tpl路径模板执行fmpp -c configfile命令;
上面的freemarker.js只是简单的使用node封装了fmpp,并没有实现批量处理,gulp-freemarker就是使用gulp实现批量执行解析任务;gulp-freemarker就是基于freemarker.js实现的gulp插件,可以方便集成其他gulp任务;
到目前为止,我们已经解决了ftl模板的批量解析处理;但前后端分离做到这里还不能满足我们的开发需要;对于异步接口,我们还需要自己维护接口和mock数据;ftl-server集成了异步接口的服务,还提供一些其他的特性:
上面的ftl-server解决了我们开发过程中的全部问题,但是实际开发前,我们要与服务端定义接口,并根据接口mock数据,这个过程,使用上面的工具都要手动完成,定义的接口也不容易在文档中管理,那么我们就需要一套接口管理服务,帮助我们管理接口,并根据接口自动化的帮我们生成需要mock的数据供我们使用;
在ftl模板中重新assign对应的list,不使用生成的mock list数据;
把mock数据中的list改成对象字符串
"list2": "[{a:1,b:2}]"
扩展fmpp, fmpp可以通过DataLoader对ftl模板访问的数据类型进行扩展;参见Stackoverflow
sourceRoot:.
outputRoot:./dist/
data: {
"word": "你好World",
"list": [{
"a": 1,
"b": 2
}
],
"list2": "[{a:1,b:2}]",
JSON:com.test.fmpp.JSONFactory()
}
在同一工程维护多个同时进行中的任务接口时,合并代码会冲突;没有一种可以按照标签或者pageId更新接口的功能,每次更新都是全局的,这样就把别人还没开发好的接口也拉下来了;
没有一个可以对全工程配置通用数据的地方,要对每个页面都加一次,例如侧栏导航数据;
针对具体工程, 需要对nei配置进行修改, 参见haitao引入nei配置和 haitao工程引入nei后开发流程
本文从工具使用介绍的角度出发,介绍了前后端分离开发的发展,到目前为止,前后端分离开发都还处于发展探索阶段,还有很长的路要走;
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者哈乐授权发布。