在采用FreeMarker做前台视图模板的情况下,我们可以通过<#include>标签和自定义宏来解决很多重复性工作。
一个简单的FreeMarker宏:
然后通过如下的形式调用: 不过这种在模板页中定义的宏能力有限。【1】假设,我们很多页面都要输出一个热门排行框,而排行数据需要从controller层动态获取,我们可以用这种宏来完成所有的展示工作,但前提是相应的controller和接口中层需要预先将这些排行数据放到model中去,因此对于后端来说这也是一个重复性的工作。那么有没有一种方式可以让后端也脱离这种重复工作呢?答案是肯定的,这也是写这篇博客的目的。在一个偶然的机会发现jeecms项目中用到了这种方式,于是借鉴了一番。
FreeMarker不仅可以在前端的模板页中定义宏,还可以通过扩展其接口在后端实现宏,这有什么好处呢?这种方式就好比让你的模板页具备了从前端再次回到后端的能力。这样我们就能很好的解决【1】处的假设,我们无需在各个controller的各个接口中去重复的向model中添加所需的排行数据,而是当FreeMarker渲染模板页时遇到相应的宏它可以回到后端去调用相应的方法取到所需的数据。例子如下:
通过实现FreeMarker的TemplateDirectiveModel就在后端实现了一个自定义的宏,这个宏的功能很简单,只是根据给定的参数将排行数据“appRankList”放到model中去,然后模板页中就可以使用这个变量了。FreeMarker的配置参数中需要将这个宏加入进去。
在模板页中使用:
这里我在模板页中又定义了一个宏,负责内容及样式的输出,因为模板页中的宏比较直观,让后端的宏只负责拿数据。其他页面直接使用“appRankBox”就可以了,然后由它来调用后端的“appRankDirective”宏来拿数据。
这样,controller就从重复工作中脱身了。
附录:代码片段(freemarker中实现自定义标签包含处理参数以及循环变量)
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.Map;
import freemarker.core.Environment;
import freemarker.template.SimpleNumber;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNumberModel;
/**
* FreeMarker 自定义标签实现重复输出内容体。
*
*
* 参数: count: 重复的次数,必须的且非负整数。 hr: 设置是否输出HTML标签 "hr" 元素. Boolean. 可选的默认为fals.
*
*
* 循环变量: 只有一个,可选的. 从1开始。
*
*
*/
public class RepeatDirective implements TemplateDirectiveModel {
private static final String PARAM_NAME_COUNT = "count";
private static final String PARAM_NAME_HR = "hr";
public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
// ---------------------------------------------------------------------
// 处理参数
int countParam = 0;
boolean countParamSet = false;
boolean hrParam = false;
Iterator paramIter = params.entrySet().iterator();
while (paramIter.hasNext()) {
Map.Entry ent = (Map.Entry) paramIter.next();
String paramName = (String) ent.getKey();
TemplateModel paramValue = (TemplateModel) ent.getValue();
if (paramName.equals(PARAM_NAME_COUNT)) {
if (!(paramValue instanceof TemplateNumberModel)) {
throw new TemplateModelException("The \"" + PARAM_NAME_HR + "\" parameter " + "must be a number.");
}
countParam = ((TemplateNumberModel) paramValue).getAsNumber().intValue();
countParamSet = true;
if (countParam < 0) {
throw new TemplateModelException("The \"" + PARAM_NAME_HR + "\" parameter " + "can't be negative.");
}
} else if (paramName.equals(PARAM_NAME_HR)) {
if (!(paramValue instanceof TemplateBooleanModel)) {
throw new TemplateModelException("The \"" + PARAM_NAME_HR + "\" parameter " + "must be a boolean.");
}
hrParam = ((TemplateBooleanModel) paramValue).getAsBoolean();
} else {
throw new TemplateModelException("Unsupported parameter: " + paramName);
}
}
if (!countParamSet) {
throw new TemplateModelException("The required \"" + PARAM_NAME_COUNT + "\" paramter" + "is missing.");
}
if (loopVars.length > 1) {
throw new TemplateModelException("At most one loop variable is allowed.");
}
// Yeah, it was long and boring...
// ---------------------------------------------------------------------
// 真正开始处理输出内容
Writer out = env.getOut();
if (body != null) {
for (int i = 0; i < countParam; i++) {
// 输出 <hr> 如果 参数hr 设置为true
if (hrParam && i != 0) {
out.write("<hr>");
}
// 设置循环变量
if (loopVars.length > 0) {
loopVars[0] = new SimpleNumber(i + 1);
}
// 执行标签内容(same as <#nested> in FTL).
body.render(env.getOut());
}
}
}
}
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
*
* 模板工具类
*/
public class FreeMarkertUtil {
/**
* @param templatePath
* 模板文件存放目录
* @param templateName
* 模板文件名称
* @param root
* 数据模型根对象
* @param templateEncoding
* 模板文件的编码方式
* @param out
* 输出流
*/
public static void processTemplate(String templatePath, String templateName, String templateEncoding, Map<?, ?> root, Writer out) {
try {
Configuration config = new Configuration();
File file = new File(templatePath);
// 设置要解析的模板所在的目录,并加载模板文件
config.setDirectoryForTemplateLoading(file);
// 设置包装器,并将对象包装为数据模型
config.setObjectWrapper(new DefaultObjectWrapper());
// 获取模板,并设置编码方式,这个编码必须要与页面中的编码格式一致
Template template = config.getTemplate(templateName, templateEncoding);
// 合并数据模型与模板
template.process(root, out);
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TemplateException e) {
e.printStackTrace();
}
}
}
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;
/**
*
* 客户端测试模板输入类
*/
public class RepeatTest {
public static void main(String[] args) {
Map<String, Object> root = new HashMap<String, Object>();
root.put("repeat", new RepeatDirective());
FreeMarkertUtil.processTemplate("src/templates", "repeat.ftl", "UTF-8", root, new OutputStreamWriter(System.out));
}
}
<#assign x = 1>
一个参数:
<@repeat count=4>
Test ${x}
<#assign x = x + 1>
</@repeat>
二个参数:
<@repeat count=3 hr=true>
Test
</@repeat>
循环变量:
<@repeat count=3; cnt>
${cnt}. Test
</@repeat>
输出结果:
- 一个参数:
- Test 1
- Test 2
- Test 3
- Test 4
- 二个参数:
- Test
- <hr> Test
- <hr> Test
- 循环变量:
- 1. Test
- 2. Test
- 3. Test
FreeMarkerConfigurer使用TemplateDirectiveModel时获取request、session
使用:
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attr.getRequest();
然后在web.xml中增加如下配置
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>