FreeMarker页面静态化
目前的项目中需要对某些页面进行静态化,减轻服务器压力。前端是用FreeMarker编写的模板。在网上查阅的使用FreeMarker静态化页面的方案大致分为两种:
1.在controller层编写生成静态页的方法,实例化模板,准备好model数据,然后通过template.process(data, out)方法将页面内容写到文件。参考【博客A】
2.扩展FreeMarker的FreeMarkerView类,覆盖doRender方法,加入根据需求静态化页面的逻辑。参考【博客B】
我选择的是第二种方案,个人觉得它更加优雅,更大程度的做到了代码重用。第一种方案中做的很多事是FreeMarker正常逻辑中就在做的,比如获取模板页的绝对路径并实例化模板,配置FreeMarkerConfigurer等。
对于静态化后的文件命名可以有两种方式:
1.约定式,比如:首页就放在/index.html中,id为9527的文章的内容就存放在/blog/content_9527.html。
2.动态式,比如:某篇文章的内容存放在/blog/20121125/5446346.html,路径根据时间和其他随机数生成。
这两种方式各有特点,方式一逻辑简单,网站中对这些静态页的链接地址直接写为约定的地址即可,但是这种方式有个明显的弊端,必须保证这些页的静态页一直都要存在,后期不好扩展,假如某个页不想静态化了,这时候就不好办了。
第二种方式相对麻烦些,需要在数据库或者其他地方存上最新的静态的页的地址,因为地址和文件是动态生成的。但是这种方式有个明显的好处即“想动就动 想静就静”。通过数据中存放的该内容的静态页地址字段可以判断该内容是否已经静态化,然后返回静态或者动态的地址。后期比较好维护。
我两种方式都用了,几个肯定要静态化的页面采用了第一种方式,其他一些页面采用了第二种方式可以动静结合。
我在【博客B】的基础上做了修改,增加了一个处理结果回调接口。因为静态化工作发生在Controller的方法return之后,而静态化成功与否与具体的Controller业务相关,比如静态化成功了需要更改数据库中相应的静态页地址。
package com.…….freemarker; import …… /** * 扩展FreeMarker,支持静态化 * @author shannon * */ public class StaticSupportFreeMarkerView extends FreeMarkerView { @Override protected void doRender(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose model to JSP tags (as request attributes). exposeModelAsRequestAttributes(model, request); // Expose all standard FreeMarker hash models. SimpleHash fmModel = buildTemplateModel(model, request, response); if (logger.isDebugEnabled()) { logger.debug("Rendering FreeMarker template [" + getUrl() + "] in FreeMarkerView '" + getBeanName() + "'"); } // Grab the locale-specific version of the template. Locale locale = RequestContextUtils.getLocale(request); /* * 如果attribute中有key为“staticSupportInfo”的StaticSupportInfo对象就表示要生成静态页 */ StaticSupportInfo staticSupportInfo = (StaticSupportInfo)request.getAttribute("staticSupportInfo"); if (null == staticSupportInfo) { processTemplate(getTemplate(locale), fmModel, response); } else { try { createHTML(getTemplate(locale), fmModel, request, response); } catch (Exception e) { staticSupportInfo.staticHtmlFail(); } } } /** * 生成静态页 * @param template * @param model * @param request * @param response * @throws IOException * @throws TemplateException * @throws ServletException */ public void createHTML(Template template, SimpleHash model, HttpServletRequest request, HttpServletResponse response) throws IOException, TemplateException, ServletException { Writer writer = response.getWriter(); //获取配置文件中的静态页存放路径 String basePath = (String)CustomProperty.getContextProperty("staticHtmlPath"); StaticSupportInfo staticSupportInfo = (StaticSupportInfo)request.getAttribute("staticSupportInfo"); if(staticSupportInfo == null || staticSupportInfo.getTargetHtml() == null) { writer.write("fail"); staticSupportInfo.staticHtmlFail(); return; } //静态页面绝对路径 String fullHtmlName = basePath + staticSupportInfo.getTargetHtml(); File htmlFile = new File(fullHtmlName); if (!htmlFile.getParentFile().exists()) { htmlFile.getParentFile().mkdirs(); } if (!htmlFile.exists()) { htmlFile.createNewFile(); } Writer out = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(htmlFile), "UTF-8")); //处理模版 template.process(model, out); out.flush(); out.close(); writer.write("success"); staticSupportInfo.staticHtmlSuccess(); } }
package com.…….freemarker; /** * 封装静态化需要的属性,如果需要得知静态化处理结果,需要实现StatusCallBack回调接口 * @author shannon * */ public class StaticSupportInfo implements java.io.Serializable { private static final long serialVersionUID = 295085193429020250L; private String targetHtml; private StatusCallBack statusCallBack; /** * 静态化成功,由StaticSupportFreeMarkerView类调用 */ public void staticHtmlSuccess() { if (statusCallBack == null) { return; } //回调 statusCallBack.success(); } /** * 静态化失败,由StaticSupportFreeMarkerView类调用 */ public void staticHtmlFail() { if (statusCallBack == null) { return; } //回调 statusCallBack.fail(); } /** * 目标html文件,除根目录外的其他路径和名字 * 如:category/app.html * @return */ public String getTargetHtml() { return targetHtml; } /** * 设置静态页面的文件名 * @param targetHtml 目标html文件,除根目录外的其他路径和名字,如:category/app.html */ public void setTargetHtml(String targetHtml) { this.targetHtml = targetHtml; } /** * @return the statusCallBack */ public StatusCallBack getStatusCallBack() { return statusCallBack; } /** * @param statusCallBack the statusCallBack to set */ public void setStatusCallBack(StatusCallBack statusCallBack) { this.statusCallBack = statusCallBack; } /** * 静态化处理结果状态回调接口 * @author shannon * */ public interface StatusCallBack { /** * 成功 */ public void success(); /** * 失败 */ public void fail(); } }
***-servlet.xml配置文件
<!-- Simple ViewResolver for FreeMarker, appending ".ftl" to logical view names. --> <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <property name="cache" value="${view.cache}" /> <!-- spring对宏的支持 --> <property name="exposeSpringMacroHelpers" value="true" /> <!-- spring 将session暴露出来 --> <property name="exposeSessionAttributes" value="true" /> <property name="exposeRequestAttributes" value="true" /> <property name="requestContextAttribute" value="rc" /> <property name="suffix" value=".ftl" /> <property name="contentType" value="text/html; charset=UTF-8" /> <property name="viewClass" value="com.…….freemarker.StaticSupportFreeMarkerView" /> </bean>
这里以首页静态化为例,以下是首页对应的Controller层方法,只是在原来的方法上添加一个createHtml参数,这样做的好处是重用。管理员 访问http://www.***.com/index.htm?createHtml=true 链接就可以生成静态页。
@RequestMapping(value = "index") @NeedNavigation public String index(HttpServletRequest request, Model model, String createHtml){ ……页面数据处理逻辑略…… //如果页面需要静态化 if (createHtml != null && "true".equals(createHtml)) { StaticSupportInfo staticSupportInfo = new StaticSupportInfo(); //设置静态化文件名 staticSupportInfo.setTargetHtml("index.html"); //以下为实现静态化处理结果回调函数,如果不关心处理结果可以不做这一步 staticSupportInfo.setStatusCallBack( new StaticSupportInfo.StatusCallBack() { public void fail() { System.out.println("静态化处理结果回调,静态化失败"); } public void success() { System.out.println("静态化处理结果回调,静态化成功"); } } ); //将静态化信息支持对象放到Attribute中,注意key值不要写错 request.setAttribute("staticSupportInfo", staticSupportInfo); } return "web/home/index"; }
注:为了安全性的考虑,应该加上验证逻辑,只能是管理员访问才静态化。
参考博客:
【博客A】
【博客B】