FreeMarker自定义指令--代码实现
Posted 专注着
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FreeMarker自定义指令--代码实现相关的知识,希望对你有一定的参考价值。
在进行FreeMarker开发时,应该都会使用到FreeMarker的指令,但是FreeMarker为我们提供指令是很有限的,因此需要我们自定义指令,实现我们需要的功能。
在我的学习过程中,遇到了一下问题(坑),记录下来,以供大家参考:
要开发指令,需要我们实现TemplateDirectiveModel接口,该接口中,需要实现execute方法。
今天给出的实例,是根据官方文档的例子而来:代码如下:
</pre></p><p><span style="color:#CC0000;"><span style="color:#000000;"></span></span><pre name="code" class="java">package com.freemarker.learn.directive.define;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
/**
* <pre>
* 自定义指令:
* 1. 需要实现TemplateDirectiveModel接口
* 2. 实现execute的方法
*
* 该指令作用: 将该标签内的内容,转换为大写
* </pre>
* @author xianglj
*/
public class UpperDirective implements TemplateDirectiveModel
@SuppressWarnings("rawtypes")
public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
throws TemplateException, IOException
//检查参数是否传入
if(!params.isEmpty())
throw new TemplateModelException("该指令不支持参数传入");
//判断是否有循环变量
if(null != loopVars && loopVars.length != 0)
throw new TemplateModelException("该指令不支持循环变量");
//判断是否有非空的嵌入内容
if(null != body)
// 执行嵌入体部分,和 FTL 中的<#nested>一样,除了
// 我们使用我们自己的 writer 来代替当前的 output writer.
body.render(new UpperCaseFilterWriter(env.getOut()));
/**
* 输出流
* @author xianglj
*/
private static class UpperCaseFilterWriter extends Writer
private final Writer out;
UpperCaseFilterWriter(Writer out)
this.out = out;
@Override
public void write(char[] cbuf, int off, int len) throws IOException
char[] transferChars = new char[len];
for(int i = 0; i < len; i++)
transferChars[i] = Character.toUpperCase(cbuf[i + off]);
out.write(transferChars);
@Override
public void flush() throws IOException
if(null != out)
out.flush();
@Override
public void close() throws IOException
if(out != null)
out.close();
该指令实现,在指令中的内容,都会被转成大写后,输出。
body的渲染部分,我们自定义一个writer,在写出字符时,先转换为大写,再写出。
最关键的部分,页面如何才能使用自定义的指令呢?有三种方式参考实现:
贴出我的模版转换工具类:
package com.freemarker.learn.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import com.alibaba.fastjson.JSONObject;
import com.freemarker.learn.directive.define.UpperDirective;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;
/**
* Created by mobao-xi on 16/4/12.
*/
public class TemplateTool
private static Map<String, Template> TEMPLATE_MAP = new HashMap<String, Template>();
private static Configuration cfg = new Configuration(new Version("2.3.23"));
static
// Don't log exceptions inside FreeMarker that it will thrown at you anyway:
cfg.setLogTemplateExceptions(false);
cfg.setSharedVariable("upper", new UpperDirective());
public static void settingConfig(ServletContext cxt)
cfg.setServletContextForTemplateLoading(cxt, "/");
public static Template getTemplate(String path) throws IOException
Template template = TEMPLATE_MAP.get(path);
if (null == template)
/*template = inputStream2String(new FileInputStream(path.toString()));
TEMPLATE_MAP.put(path, template);*/
template = cfg.getTemplate(path);
TEMPLATE_MAP.put(path, template);
return template;
public static String getTemplate(String path, Object data) throws Exception
Template template = getTemplate(path);
//StringTemplateLoader loader = new StringTemplateLoader();
//loader.putTemplate("", source);
//cfg.setTemplateLoader(loader);
cfg.setDefaultEncoding("UTF-8");
//Template template = cfg.getTemplate("");
StringWriter writer = new StringWriter();
template.process(data, writer);
String source = writer.toString();
return source;
/**
* 将stream 转成字符串
*
* @param is
* @return
* @throws IOException
*/
private static String inputStream2String(InputStream is) throws IOException
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int i = -1;
while ((i = is.read()) != -1)
baos.write(i);
return baos.toString();
1. 将数据放在数据模型中。
以下为我的模型的封装:
package com.freemarker.learn.response;
import java.util.HashMap;
import java.util.Map;
import com.freemarker.learn.annotation.Abbr;
import com.freemarker.learn.directive.define.UpperDirective;
/**
* 自定义指令返回实例。
* @author xianglj
*/
@Abbr(name="directive/upper")
public class UpperDirectiveRespEntity extends ResponseEntity
private Map<String, Object> dataMap ;
@Override
public Map<String, Object> getData()
return dataMap;
@Override
public String getFtlPath()
return "/pages/templates/upper_directive.html";
@Override
public void generateData()
dataMap = new HashMap<String, Object>();
dataMap.put("upper", new UpperDirective());
通过以上代码,我们发现,我返回的数据模型是一个HashMap对象,在对象中我存储了一个 TemplateDirectiveModel 的子类,也就是我们定义的指令对象。而我对应的模版路径是在 " /pages/templates/upper_directive.html ",在该页面中,我就能够直接通过 <@upper></@upper>自定义指令方式进行使用。
2、 第二种方式,将自定义的指令,存放在Configuration公共变量中。也许第一次可能不会明白,其实就是在Configuration完成之后,调用
setSharedVariable(String name, Object obj)方法进行设置即可。
具体代码如下:具体参考上面(TemplateTool.java 的实现代码)
cfg.setSharedVariable("upper", new UpperDirective());
3. 在模版界面中进行创建,使用<#assign ""?new()>
<#assign upper="com.freemarker.learn.directive.define.UpperDirective"?new()>
upper_directive.html代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>自定义指令upper的使用</title>
</head>
<body>
upper 外<br/>
<@upper>
upper 内<br>
<#list ["red", "blue", "green"] as c>
$c<br>
</#list>
</@upper>
<#assign upper="com.freemarker.learn.directive.define.UpperDirective"?new()>
</body>
</html>
给出输出结果:
做到这里,也许大家会有一个疑问,这三种方式是否可以同时使用,是否会存在冲突?
经过测试,发现这三种方式同时使用时,不会存在冲突。因此我做了一个猜想,可能FreeMarker在查找自定义顺序为:
模版页面 -> 数据模型(data-model) -> Configuration 公共变量
具体我也没有查证过是否正确。如果有知道的高人,请指出一下。
碰到过的坑:
在学习过程中,难免会遇到各种各样的坑,最大的坑就是进行模版和数据结合时,始终不能找到自定义指令,异常信息为:
freemarker.core.NonUserDefinedDirectiveLikeException: [... Exception message was already printed; see it above ...]
at freemarker.core.UnifiedCall.accept(UnifiedCall.java:113)
at freemarker.core.Environment.visit(Environment.java:324)
at freemarker.core.MixedContent.accept(MixedContent.java:54)
at freemarker.core.Environment.visit(Environment.java:324)
at freemarker.core.Environment.process(Environment.java:302)
at freemarker.template.Template.process(Template.java:325)
at com.freemarker.learn.util.TemplateTool.getTemplate(TemplateTool.java:59)
at com.freemarker.learn.servlet.base.BaseServlet.doGet(BaseServlet.java:45)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:522)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1095)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Unknown Source)
始终无法找到自定义指令,最根本的原因在于:
public static String getTemplate(String path, Object data) throws Exception
Template template = getTemplate(path);
//StringTemplateLoader loader = new StringTemplateLoader();
//loader.putTemplate("", source);
//cfg.setTemplateLoader(loader);
cfg.setDefaultEncoding("UTF-8");
//Template template = cfg.getTemplate("");
StringWriter writer = new StringWriter();
template.process(JSONObject.toJSON(data), writer);
String source = writer.toString();
return source;
我在进行模版和数据模型进行合并时,将数据模型解析为JSON在进行合并,这样就会存在一个很严重的问题,FreeMarker不能将指令对象正确解析,因此在找到upper时,发现upper不是TemplateDirectiveModel对象,出现以上异常。
希望以上的内容,会对你有所帮助。
以上是关于FreeMarker自定义指令--代码实现的主要内容,如果未能解决你的问题,请参考以下文章
springboot 使用freemarker作为展现后,怎么加入自定义指令