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】


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章