freemarker现有的一些内置函数不能满足我们的需求,这些内置函数包括:chunk, is_date, last, root, j_string, contains, is_hash, long, float, ends_with, namespace, matches, time, values, seq_last_index_of, uncap_first, byte, substring, is_transform, web_safe, groups, seq_contains, is_macro, index_of, word_list, int, is_method, eval, parent, xml, number, capitalize, if_exists, rtf, node_type, double, is_directive, url, size, default, is_boolean, split, node_name, is_enumerable, seq_index_of, is_sequence, sort, is_node, sort_by, left_pad, cap_first, interpret, children, node_namespace, chop_linebreak, date, short, last_index_of, is_collection, ancestors, length, trim, datetime, is_string, reverse, c, keys, upper_case, js_string, has_content, right_pad, replace, is_hash_ex, new, is_number, is_indexable, lower_case, string, exists, html, first, starts_with
这时freemarker的灵活,为我们提供了很好的扩展方案,自定义的函数,可以方便我们在写到画面的变量进一步计算出我们想要的结果,自定义的标签指令可以对一些画面共通的部分作出有效简单复用的操作,当然也可以实现自定义函数中的功能,不过,对于不同的分工还是要用不同的技术才好,效率和使用才会更优。
(一) 自定义函数:
写一个类继承TemplateMethodModel,比如说:
<span style="font-size:14px;">public class TruncateTemplateMethodModel implements TemplateMethodModel {
public Object exec(List arguments) throws TemplateModelException {
return arguments.get(0).toString().substring(0,1);
}
}</span>
<span style="font-size:14px;"> Map root = new HashMap();
root.put("truncate",new TruncateTemplateMethodModel();
Template temp = cfg.getTemplate("a.ftl");
StringWriter out = new StringWriter();
temp.process(root, out);
out.flush();
System.out.println(out.getBuffer().toString());</span>
a.ftl为:${truncate("abc")}
运行后将会输出: a
若与spring集成的话,也很简单,只要在:*-servlet.xml中加入以下代码即可:
<span style="font-size:14px;"><bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="suffix" value=".ftl"/>
<property name="contentType" value="text/html;charset=UTF-8"/>
<property name="exposeRequestAttributes" value="true"/>
<property name="exposeSessionAttributes" value="true"/>
<property name="attributesMap">
<map>
<entry key="truncate"><bean class="com.rj8g.linkage.renba.web.TruncateTemplateMethodModel" /></entry>
</map>
</property>
</bean></span>
例2
使用类似格式 ${avg(10, 20)} 其中avg为函数名,10,20为传入的参数
定义在前台:
<span style="font-size:14px;"><#function name param1 param2 ... paramN>
...
<#return returnValue>
...
</#function></span>
<span style="font-size:14px;"><#function avg x y>
<#return (x + y) / 2>
</#function></span>
输出结果:15
定义在后台:
方法变量在存于实现了TemplateMethodModel接口的模板中。这个接口仅包含一个方法:TemplateModel exec(java.util.List arguments)。当使用方法调用表达式调用方法时,exec方法将会被调用。形参将会包含FTL方法调用形参的值。exec方法的返回值给出了FTL方法调用表达式的返回值。
TemplateMethodModelEx接口扩展了TemplateMethodModel接口。它没有任何新增的方法。事实上这个对象实现这个标记接口暗示给FTL引擎,形式参数应该直接以TemplateModel-s形式放进java.util.List。否则将会以String-s形式放入List。
<span style="font-size:14px;">public class IndexOfMethod implements TemplateMethodModel {
public TemplateModel exec(List args) throws TemplateModelException {
if (args.size() != 2) {
throw new TemplateModelException("Wrong arguments");
}
return new SimpleNumber(((String) args.get(1)).indexOf((String) args.get(0)));
}
}</span>
然后将实例放入到根数据模型中:
<span style="font-size:14px;">root.put("indexOf", new IndexOfMethod());</span>
<span style="font-size:14px;"><#assign x = "something">
${indexOf("met", x)}
${indexOf("foo", x)}</span>
输出结果:
2 -1
(二) 自定义指令
使用以下格式调用自定义指令:
<@user_def_dir_exp param1=val1 param2=val2 ... paramN=valN/>
定义在前台:
<#macro name param1 param2 ... paramN>
...
<#nested loopvar1, loopvar2, ..., loopvarN>
...
<#return>
...
</#macro>
例子:
<#macro test foo bar baaz>
Test text, and the params: ${foo}, ${bar}, ${baaz}
</#macro>
<#-- call the macro: -->
<@test foo="a" bar="b" baaz=5*5-2/>
输出结果:
Test text, and the params: a, b, 23
定义在后台:
Java程序员可以使用TemplateDirectiveModel接口在Java代码中实现自定义指令。详情可以参加API文档。
注意:
TemplateDirectiveModel在FreeMarker 2.3.11版本时才加入。用来代替快被废弃的TemplateTransformModel。
public class UpperDirective implements TemplateDirectiveModel {
public void execute(Environment env, Map params,
TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
// 检查参数是否传入
if (!params.isEmpty()) {
throw new TemplateModelException("This directive doesn't allow parameters.");
}
if (loopVars.length != 0) {
throw new TemplateModelException("This directive doesn't allow loop variables.");
}
// 是否有非空的嵌入内容
if (body != null) {
// 执行嵌入体部分,和FTL中的<#nested>一样,除了
// 我们使用我们自己的writer来代替当前的output writer.
body.render(new UpperCaseFilterWriter(env.getOut()));
} else {
throw new RuntimeException("missing body");
}
}
/**
* {@link Writer}改变字符流到大写形式,
* 而且把它发送到另外一个{@link Writer}中。
*/
private static class UpperCaseFilterWriter extends Writer {
private final Writer out;
UpperCaseFilterWriter (Writer out) {
this.out = out;
}
public void write(char[] cbuf, int off, int len)
throws IOException {
char[] transformedCbuf = new char[len];
for (int i = 0; i < len; i++) {
transformedCbuf[i] = Character.
toUpperCase(cbuf[i + off]);
}
out.write(transformedCbuf);
}
public void flush() throws IOException {
out.flush();
}
}
例子:
foo
<@upper>
bar
<#-- 这里允许使用所有的FTL -->
<#list ["red", "green", "blue"] as color>
${color}
</#list>
baaz
</@upper>
wombat
输出结果:
foo
BAR
RED
GREEN
BLUE
BAAZ
wombat