最近在客户需求的要求就定制开发一套CMS,模板采用了Freemarker。最初的时候是设想用Freemarker标签开发的模板,也做网站页面的模板。然后可以直接在页面模板中使用开发好的标签,来显示网站内容。但总还是觉得这样的模板不够灵活,局限性比较大。后来通过和领导的商议决定采用一下方式,最终可以直接在Freemarker页面模板层对已封装好的API进行调用,可以实现在模板中的简单编程。
这种做法也是在探索中,如果大家觉得有什么不妥之处还望点拨。
- 模板存放在数据库中(通常我们都是编写Freemarker的ftl模板文件)
- 采用StringTemplateLoader
- 采用BeansWrapper
首先我们要开发一个Servlet去拦截网站的请求,因为我们要去解析模板。FreeMarker为我们提供了一个现成的Servlet:freemarker.ext.servlet.FreemarkerServlet。但是这个不能满足我们的要求,它是按照URL请求的文件名称来去查找解析模板的,例如/article/demo.ftl,那么它只能去模板存放目录寻找demo.ftl模板。呵呵,其实这个 Servlet是未来让FreeMarker代替jsp使用的,而且他的它也无法装载数据库中的模板信息,所以我就参考它开发了自己的Servlet。
package freemarker.ext.servlet;
/**
* 参考自freemarker.ext.servlet.FreemarkerServlet
* 支持自定义标签的使用,支持自定义扩展名拦截.
*/
public class FreeMarkerStringTemplateViewServlet extends
javax.servlet.http.HttpServlet {
/**
* 为子类提供Log功能,方便子类使用
*/
protected Log log = LogFactory.getLog(getClass());
/** TemplatePath */
private static final String TEMPLATE_PATH = "TemplatePath";
/** NoCache */
private static final String NOCACHE = "NoCache";
/** TemplateDelay */
private static final String TEMPLATE_DELAY = "template_update_delay";
/** DefaultEncoding */
private static final String DEF_ENCODING = "default_encoding";
/** Request */
public static final String KEY_REQUEST = "Request";
/** __FreeMarkerServlet.Request__ */
public static final String KEY_REQUEST_PRIVATE =
"__FreeMarkerServlet.Request__";
/** RequestParameters */
public static final String KEY_REQUEST_PARAMETERS = "RequestParameters";
/** Session */
public static final String KEY_SESSION = "Session";
/** Application */
public static final String KEY_APPLICATION = "Application";
/** __FreeMarkerServlet.Application__ */
public static final String KEY_APPLICATION_PRIVATE =
"__FreeMarkerServlet.Application__";
/** JspTaglibs */
public static final String KEY_JSP_TAGLIBS = "JspTaglibs";
/** .freemarker.Request */
private static final String ATTR_REQUEST_MODEL = ".freemarker.Request";
/** .freemarker.RequestParameters */
private static final String ATTR_REQUEST_PARAMETERS_MODEL =
".freemarker.RequestParameters";
/** .freemarker.Session */
private static final String ATTR_SESSION_MODEL = ".freemarker.Session";
/** .freemarker.Application */
private static final String ATTR_APPLICATION_MODEL =
".freemarker.Application";
/** .freemarker.JspTaglibs */
private static final String ATTR_JSP_TAGLIBS_MODEL =
".freemarker.JspTaglibs";
/** 日期 */
private static final String EXPIRATION_DATE;
static {
GregorianCalendar expiration = new GregorianCalendar();
expiration.roll(Calendar.YEAR, -1);
SimpleDateFormat httpDate = new SimpleDateFormat(
"yyyy-MMM-dd HH:mm:ss", java.util.Locale.CHINA);
EXPIRATION_DATE = httpDate.format(expiration.getTime());
}
/**
* response返回是否使用缓存
*/
private boolean nocache;
/**
* 创新Freemarker模板的必要条件
*/
private Configuration config;
/**
* 采用BEANS_WRAPPER
*/
private ObjectWrapper wrapper;
/**
* text/html
*/
private String contentType;
/**
* 采用StringLoader,从数据库中读取模板信息
*/
//private StringTemplateLoader strTmpt;
/**
* Servlet 初始化
*/
public void init() throws ServletException {
try {
config = new Configuration();
config.setTemplateExceptionHandler(
TemplateExceptionHandler.HTML_DEBUG_HANDLER);
contentType = "text/html";
// 采用BEANS_WRAPPER
wrapper = ObjectWrapper.BEANS_WRAPPER;
config.setObjectWrapper(wrapper);
// 初始化所有的Servlet参数
Enumeration initpnames = getServletConfig().getInitParameterNames();
while (initpnames.hasMoreElements()) {
String name = (String) initpnames.nextElement();
String value = getInitParameter(name);
if (name == null) {
throw new ServletException(this.getClass().toString()
+ "需要一些初始化参数,web.xml可能尚未完成.");
}
if (value == null) {
throw new ServletException(this.getClass().toString()
+ "有部分初始化参数未被赋值,web.xml可能尚未完成.");
}
if (name.equals(TEMPLATE_PATH)) {
// ignore: we have already processed these do nothing..
log.debug("忽略" + TEMPLATE_PATH);
} else if (name.equals(DEF_ENCODING)) { // set DefaultEncoding
log.debug(DEF_ENCODING + " value is:" + value);
config.setDefaultEncoding(value);
} else if (name.equals(TEMPLATE_DELAY)) { // 模板延迟更新时间
try {
log.debug(TEMPLATE_DELAY + " value is:" + value);
config.setTemplateUpdateDelay(Integer.parseInt(value));
} catch (NumberFormatException e) {
throw new ServletException(e.getMessage() + ". '"
+ TEMPLATE_DELAY + "'必须是整数");
}
} else if (name.equals(NOCACHE)) { // 设置缓存
log.debug(NOCACHE + " value is :" + value);
nocache = StringUtil.getYesNo(value);
} else {
// 设置其它参数,嘿嘿,如果参数名称不符合Configuration要求肯定要Exception
config.setSetting(name, value);
}
}
} catch (ServletException e) {
throw e;
} catch (Exception e) {
throw new ServletException(e);
}
}
/** Get请求 */
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
process(request, response);
}
/** Post请求 */
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
process(request, response);
}
/**
* 向StringTemplateLoader中添加模板内容
* @throws ServletException
*/
private StringTemplateLoader addStringTemplate(
StringTemplateContext strTmpCtx) throws ServletException {
if (strTmpCtx == null) {
throw new ServletException("StringTemplateContext is null");
}
if (strTmpCtx.getTemplateContent() != null) {
StringTemplateLoader stl = getStrTmpt();
stl.putTemplate(strTmpCtx.getTemplateName(),
strTmpCtx.getTemplateContent());
setStrTmpt(stl);
//重写添加TemplateLoader
log.debug("StringTemplateLoader成功添加模板'"
+ strTmpCtx.getTemplateName() + "'");
}
return getStrTmpt();
}
/**
* 模板解析过程
* @throws ParseURLToTemplateException
*/
private void process(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
StringTemplateLoader strTmpt = getStrTmpt();
if (strTmpt == null) {
strTmpt = new StringTemplateLoader();
setStrTmpt(strTmpt);
}
// 自定义类型StringTemplateContext,存放[模板名称]和[模板内容]
StringTemplateContext strTmpCtx = null;
try {
strTmpCtx = reqUrlToModelCtx(req, strTmpt);
} catch (ParseURLToTemplateException pe) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
pe.printStackTrace();
}
//添加模板
strTmpt = addStringTemplate(strTmpCtx);
config.setTemplateLoader(strTmpt);
Template template = config.getTemplate(strTmpCtx.getTemplateName());
Object attrContentType = template.getCustomAttribute("content_type");
if (attrContentType != null) {
resp.setContentType(attrContentType.toString());
} else {
resp.setContentType(contentType + "; charset="
+ template.getEncoding());
}
setBrowserCaching(resp);
ServletContext servletContext = getServletContext();
try {
TemplateModel model = createModel(wrapper, servletContext, req,
resp, strTmpCtx.getModel());
template.process(model, resp.getWriter());
} catch (TemplateException te) {
ServletException e = new ServletException(
"Error executing FreeMarker template", te);
try {
e.getClass().getMethod("initCause",
new Class[] { Throwable.class }).invoke(e,
new Object[] { te });
} catch (Exception ex) {
// Can't set init cause, we're probably running on a pre-1.4
// JDK, oh well...
}
throw e;
}
}
/**
* 创建Freemarker模板的model
*/
protected TemplateModel createModel(ObjectWrapper wrap,
ServletContext servletContext, HttpServletRequest request,
HttpServletResponse response, Map model)
throws TemplateModelException {
AllHttpScopesHashModel params = new AllHttpScopesHashModel(wrap,
servletContext, request);
// Create hash model wrapper for servlet context (the application)
ServletContextHashModel servletContextModel =
(ServletContextHashModel) servletContext
.getAttribute(ATTR_APPLICATION_MODEL);
if (servletContextModel == null) {
servletContextModel = new ServletContextHashModel(this, wrap);
servletContext.setAttribute(ATTR_APPLICATION_MODEL,
servletContextModel);
TaglibFactory taglibs = new TaglibFactory(servletContext);
servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL, taglibs);
}
params.putUnlistedModel(KEY_APPLICATION, servletContextModel);
params.putUnlistedModel(KEY_APPLICATION_PRIVATE, servletContextModel);
params.putUnlistedModel(KEY_JSP_TAGLIBS, (TemplateModel) servletContext
.getAttribute(ATTR_JSP_TAGLIBS_MODEL));
// Create hash model wrapper for session
HttpSessionHashModel sessionModel;
HttpSession session = request.getSession();
sessionModel = (HttpSessionHashModel) session
.getAttribute(ATTR_SESSION_MODEL);
if (sessionModel == null || sessionModel.isZombie()) {
sessionModel = new HttpSessionHashModel(session, wrap);
session.setAttribute(ATTR_SESSION_MODEL, sessionModel);
}
params.putUnlistedModel(KEY_SESSION, sessionModel);
// Create hash model wrapper for request
HttpRequestHashModel requestModel = (HttpRequestHashModel) request
.getAttribute(ATTR_REQUEST_MODEL);
if (requestModel == null || requestModel.getRequest() != request) {
requestModel = new HttpRequestHashModel(request, response, wrap);
request.setAttribute(ATTR_REQUEST_MODEL, requestModel);
request.setAttribute(ATTR_REQUEST_PARAMETERS_MODEL,
new HttpRequestParametersHashModel(request));
}
params.putUnlistedModel(KEY_REQUEST, requestModel);
params.putUnlistedModel(KEY_REQUEST_PRIVATE, requestModel);
// Create hash model wrapper for request parameters
HttpRequestParametersHashModel requestParametersModel =
(HttpRequestParametersHashModel) request
.getAttribute(ATTR_REQUEST_PARAMETERS_MODEL);
params.putUnlistedModel(KEY_REQUEST_PARAMETERS, requestParametersModel);
params.putAll(model);
return params;
}
/**
* 需要有自类重写,返回需要的TemplateModelContext[templateName Model]
*
* @return
* @throws ParseURLToTemplateException
* @throws Exception
*/
protected StringTemplateContext reqUrlToModelCtx(HttpServletRequest req,
StringTemplateLoader tmLoader) throws ParseURLToTemplateException {
// 需要子类去重写,完成封装数据
return null;
}
/**
* If the parameter "nocache" was set to true, generate a set of headers
* that will advise the HTTP client not to cache the returned page.
*/
private void setBrowserCaching(HttpServletResponse res) {
if (nocache) {
// HTTP/1.1 + IE extensions
res.setHeader("Cache-Control",
"no-store, no-cache, must-revalidate, "
+ "post-check=0, pre-check=0");
// HTTP/1.0
res.setHeader("Pragma", "no-cache");
// Last resort for those that ignore all of the above
res.setHeader("Expires", EXPIRATION_DATE);
}
}
/**
* getter strTmpt
* @return
*/
private StringTemplateLoader getStrTmpt() {
return (StringTemplateLoader) getServletContext().getAttribute(
"stringTemplateLoader");
}
/**
* setter strTmpt
* @param strTmpt
*/
private void setStrTmpt(StringTemplateLoader strTmpt) {
getServletContext().setAttribute("stringTemplateLoader", strTmpt);
}
}
未完待续....就像这人世间错综复杂、乱七八糟的种种破事