freemarker 自定义TemplateDirectiveModel

本文介绍了如何在FreeMarker中自定义TemplateDirectiveModel,通过示例展示了如何在后端实现宏并将其添加到FreeMarker配置中,从而减少Controller中的重复代码,提高代码复用性和模板渲染效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在采用FreeMarker做前台视图模板的情况下,我们可以通过<#include>标签和自定义宏来解决很多重复性工作。

一个简单的FreeMarker宏:

[html] view plain copy
  1. <#macro sayHello name="">  
  2.     hello ${name}  
  3. </#macro>  
然后通过如下的形式调用:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <@sayHello name="shannon" />  
不过这种在模板页中定义的宏能力有限。【1】假设,我们很多页面都要输出一个热门排行框,而排行数据需要从controller层动态获取,我们可以用这种宏来完成所有的展示工作,但前提是相应的controller和接口中层需要预先将这些排行数据放到model中去,因此对于后端来说这也是一个重复性的工作。那么有没有一种方式可以让后端也脱离这种重复工作呢?答案是肯定的,这也是写这篇博客的目的。

在一个偶然的机会发现jeecms项目中用到了这种方式,于是借鉴了一番。

FreeMarker不仅可以在前端的模板页中定义宏,还可以通过扩展其接口在后端实现宏,这有什么好处呢?这种方式就好比让你的模板页具备了从前端再次回到后端的能力。这样我们就能很好的解决【1】处的假设,我们无需在各个controller的各个接口中去重复的向model中添加所需的排行数据,而是当FreeMarker渲染模板页时遇到相应的宏它可以回到后端去调用相应的方法取到所需的数据。例子如下:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. import freemarker.core.Environment;  
  2. import freemarker.template.ObjectWrapper;  
  3. import freemarker.template.TemplateDirectiveModel;  
  4.   
  5. /** 
  6.  * FreeMarker自定义宏 
  7.  * 获取App下载排行列表 
  8.  * 参数包括 length(列表长度) mtypeCode(主类型代码) typeCode(小类型代码) rankMode(排行模式1、2、3) 
  9.  * @author shannon 
  10.  * 
  11.  */  
  12. public class FMAppRankDirective implements TemplateDirectiveModel {  
  13.   
  14.     @Resource(name = "appRankService")  
  15.     private AppRankService appRankService;  
  16.       
  17.       
  18.     @SuppressWarnings("unchecked")  
  19.     @Override  
  20.     public void execute(Environment env, Map params, TemplateModel[] loopVars,  
  21.             TemplateDirectiveBody body) throws TemplateException, IOException {  
  22.         //DirectiveUtils是借用jeecms项目中的工具类,主要是因为它集成了一些异常处理功能,  
  23.         //其实完全可以不用它,params是个Map,自己通过key取值就可以了,做一下空值判断  
  24.         Integer length = DirectiveUtils.getInt("length", params);  
  25.         Integer mtypeCode = DirectiveUtils.getInt("mtypeCode", params);  
  26.         Integer typeCode = DirectiveUtils.getInt("typeCode", params);  
  27.         Integer rankMode = DirectiveUtils.getInt("rankMode", params);  
  28.         ArrayList<App> rankList = appRankService.getRankList(length, mtypeCode, typeCode, 
  29. rankMode);  
  30.           
  31.         env.setVariable("appRankList", ObjectWrapper.DEFAULT_WRAPPER.wrap(rankList));  
  32.         if (body != null) {  
  33.             body.render(env.getOut());  
  34.         }  
  35.     }  
  36. }  
通过实现FreeMarker的TemplateDirectiveModel就在后端实现了一个自定义的宏,这个宏的功能很简单,只是根据给定的参数将排行数据“appRankList”放到model中去,然后模板页中就可以使用这个变量了。

FreeMarker的配置参数中需要将这个宏加入进去。

[html] view plain copy
  1. <bean id="appRankDirective" class="com.shannon.example.rank.util.FMAppRankDirective" />  
  2. <bean id="freemarkerConfigurer" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">  
  3.     ……其他配置略……  
  4.     <property name="freemarkerVariables">  
  5.         <map>  
  6.             ……其他配置略……  
  7.             <entry key="appRankDirective" value-ref="appRankDirective"/>  
  8.         </map>  
  9.     </property>  
  10. </bean>  

在模板页中使用:
[html] view plain copy
  1. <#-- 应用下载排行框,title为该框的标题,length为排行列表长度,mtypeCode为主类型代码,typeCode为小类型代码,rankMode为排行方式   
  2. 1为总下载量,2为月下载量,3为昨日增长下载量  
  3. -->  
  4. <#macro appRankBox title="" length=10 mtypeCode=1 typeCode=-1 rankMode=1>  
  5.       <@appRankDirective length=length mtypeCode=mtypeCode typeCode=typeCode rankMode=rankMode />  
  6.         <h3 class="box-title">${title}</h3>  
  7.     <div class="box">  
  8.       <ul class="row-list">  
  9.         <#list appRankList as item>  
  10.         ……详细输出内容略……  
  11.         </#list>  
  12.         </ul>  
  13.     </div>  
  14. </#macro>  

这里我在模板页中又定义了一个宏,负责内容及样式的输出,因为模板页中的宏比较直观,让后端的宏只负责拿数据。其他页面直接使用“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>   

 输出结果:

Java代码
  1. 一个参数:   
  2.   Test 1  
  3.   Test 2  
  4.   Test 3  
  5.   Test 4  
  6.   
  7. 二个参数:   
  8.   Test   
  9. <hr>  Test   
  10. <hr>  Test   
  11.   
  12. 循环变量:   
  13.   1. Test   
  14.   2. Test   
  15.   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>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值