博学,切问,近思--詹子知 (https://jameszhan.github.io)
这篇文章我们将不介绍Freemarker的基本语法,先来个工具模板utils.ftl,因为下面的操作中会用到这个文件。
<#macro mapping map>
<#list map?keys as key>
${key}: ${(map[key])!}
</#list>
</#macro>
<#macro listing list>
<#list list as item>
${item}
</#list>
</#macro>
<#macro repeat count>
<#list 1..count as i>${i}<#nested></#list>
</#macro>
1. Model 的注册
Map<String, Object> rootMap = new HashMap<String, Object>();
rootMap.put("hello", "James");
rootMap.put("env", System.getenv());
rootMap.put("this", rootMap);
Configuration cfg = new Configuration();
Template template = cfg.getTemplate("model.ftl", locale);
template.process(rootMap, out);
//model.ftl content is
<#import "utils.ftl" as util />
Hello ${hello}
this hello ${this.hello}
<@util.mapping env />
Output is:
Hello James
this hello James
USERPROFILE: C:/Documents and Settings/011447.SZ
PATHEXT: .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH
USERDNSDOMAIN: SZ.VSG.COM
__COMPAT_LAYER: EnableNXShowUI
JAVA_HOME: D:/toolkit/sdk/jdk1.6.0_14
CURL_HOME: D:/toolkit/sdk/curl-7.19.5
MINGW_HOME: D:/toolkit/sdk/mingw
SystemDrive: C:
TEMP: C:/DOCUME~1/011447.SZ/LOCALS~1/Temp
MYSQL_HOME: D:/toolkit/sdk/mysql-5.1.35-win32
ProgramFiles: C:/Program Files
Path: D:/toolkit/sdk/jdk1.6.0_14/bin/../jre/bin/client;D:/toolkit/sdk/jdk1.6.0_14/bin/../jre/bin;D:/toolkit/sdk/jdk1.6.0_14/bin/../jre/lib/i386;D:/msys/bin;D:/toolkit/sdk/ParserGenerator2/Bin;D:/toolkit/sdk/mingw/bin;D:/toolkit/sdk/curl-7.19.5/;C:/Python31/;D:/toolkit/sdk/apache-tomcat-6.0.20/bin;D:/toolkit/sdk/jdk1.6.0_14/bin;D:/toolkit/sdk/CLisp;D:/toolkit/sdk/mysql-5.1.35-win32/bin;D:/toolkit/sdk/javafx-sdk1.2/bin;D:/toolkit/sdk/javafx-sdk1.2/emulator/bin;C:/WINDOWS/system32;C:/WINDOWS;C:/WINDOWS/System32/Wbem;C:/Program Files/QuickTime/QTSystem/;C:/Program Files/StormII/Codec/QTSystem/
HOMEDRIVE: C:
PROCESSOR_REVISION: 170a
CLIENTNAME: Console
USERDOMAIN: SZ
QTJAVA: D:/toolkit/sdk/jre6/lib/ext/QTJava.zip
ALLUSERSPROFILE: C:/Documents and Settings/All Users.WINDOWS
PROCESSOR_IDENTIFIER: x86 Family 6 Model 23 Stepping 10, GenuineIntel
SESSIONNAME: Console
YACC_HOME: D:/toolkit/sdk/ParserGenerator2
AVR32_HOME: D:/toolkit/WinAVR-20100110
TMP: C:/DOCUME~1/011447.SZ/LOCALS~1/Temp
PYTHON_HOME: C:/Python31
PROCESSOR_ARCHITECTURE: x86
LOGONSERVER: //SZ-DC-01
=::: ::/
CLASSPATH: .;D:/toolkit/sdk/jre6/lib/ext/QTJava.zip
CommonProgramFiles: C:/Program Files/Common Files
OS: Windows_NT
FP_NO_HOST_CHECK: NO
HOMEPATH: /Documents and Settings/011447.SZ
PROCESSOR_LEVEL: 6
CATALINA_HOME: D:/toolkit/sdk/apache-tomcat-6.0.20
LISP_HOME: D:/toolkit/sdk/CLisp
COMPUTERNAME: SZ-EXP-011447
SystemRoot: C:/WINDOWS
windir: C:/WINDOWS
NUMBER_OF_PROCESSORS: 2
USERNAME: 011447
TOMCAT_HOME: D:/toolkit/sdk/apache-tomcat-6.0.20
ComSpec: C:/WINDOWS/system32/cmd.exe
MSYS_HOME: D:/msys
APPDATA: d:/user/011447/Application Data
在做这个事情之前,我们先准备一些工具方法。
//拿到静态Class的Model
public TemplateModel useClass(String className) throws TemplateModelException
{
BeansWrapper wrapper = (BeansWrapper) cfg.getObjectWrapper();
TemplateHashModel staticModels = wrapper.getStaticModels();
return staticModels.get(className);
}
//拿到目标对象的model
public TemplateModel useObjectModel(Object target) throws TemplateModelException
{
ObjectWrapper wrapper = cfg.getObjectWrapper();
TemplateModel model = wrapper.wrap(target);
return model;
}
//拿到目标对象某个方法的Model
public TemplateModel useObjectMethod(Object target, String methodName) throws TemplateModelException
{
TemplateHashModel model = (TemplateHashModel) useObjectModel(target);
return model.get(methodName);
}
2.静态类注册,有的时候,我们经常需要在freemarker页面中使用java中的一些静态类的方法和属性,下面就演示这种情形的注册方式。
Map<String, Object> rootMap = new HashMap<String, Object>();
rootMap.put("static", ((BeansWrapper)cfg.getObjectWrapper()).getStaticModels());
rootMap.put("thread", useClass("java.lang.Thread"));
rootMap.put("system", useClass("java.lang.System"));
//利用Freemarker在控制台输出一条消息: "Hello World"
${system.out.println("Hello World!")}
//格式化字符串输出:
${static["java.lang.String"].format("%-20s%-20s%-20s", "", thread.currentThread().getName(), thread.activeCount())}
3.对象注册
rootMap.put("currentTime", Calendar.getInstance());
//如果不想暴露整个对象的信息,就可以使用单个方法的注册形式。
rootMap.put("dateformat", useObjectMethod(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss S"), "format"));
//Sample:
Current time is ${dateformat(currentTime.time)}
TimeZone: ${currentTime.timeZone.ID}-${currentTime.timeZone.displayName}
Output:
Current time is 2010-08-17 02:06:11 48
TimeZone: Asia/Shanghai-中国标准时间
4.注册共享对象,有的时候我们可能希望注册的对象能够被所有的freemarker文件访问得到,那么可以使用configuration.setSharedVariable(String name, TemplateModel tm) 方法注册Model。
5.Dot扩展
很多时候,我们可能希望为我们的model定义一个有层次结构意义的名字,比如"system.admin", "user.id" etc... 由于.在Freemarker的解析中有特殊的含义,所以如果我们注册的Model注册的名字中如果含有.的话,你就无法在页面上拿到正确的对象信息。下面这个类提供了Dot扩展的功能,注意registerGlobalModel方法。
package com.apple.ext.freemarker;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Locale;
import freemarker.cache.ClassTemplateLoader;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.MethodCheckingObjectWrapper;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
public class ModelRegistrar {
private Configuration cfg;
public ModelRegistrar()
{
cfg = new Configuration();
cfg.setTemplateLoader(new ClassTemplateLoader(this.getClass(), ""));
cfg.setObjectWrapper(new MethodCheckingObjectWrapper());
}
public ModelRegistrar(Configuration cfg)
{
this.cfg = cfg;
}
public Configuration getConfiguration()
{
return cfg;
}
public void registerGlobalMethod(String key, Object target, String methodName) throws TemplateModelException
{
registerGlobalModel(key, useObjectMethod(target, methodName));
}
public void registerGlobalModel(String key, Object target) throws TemplateModelException
{
String[] keys = key.split("//.");
if (keys.length < 1)
{
throw new IllegalArgumentException("Invalid model registration key: " + key);
}
TemplateModel model = cfg.getObjectWrapper().wrap(target);
TemplateModel exist = cfg.getSharedVariable(keys[0]);
if (exist == null)
{
cfg.setSharedVariable(keys[0], buildDeepTemplateMap(keys, 1, model));
}
else
{
MapDotModel registry = null;
int i = 0;
for (; i < keys.length - 1; i++)
{
if (exist == null)
{
registry.put(keys[i], buildDeepTemplateMap(keys, i + 1, model));
return;
}
if (exist instanceof MapDotModel)
{
registry = (MapDotModel) exist;
exist = registry.get(keys[i + 1]);
}
else
{
throw new IllegalArgumentException("model '" + key + "' at '" + keys[i]
+ "' already defined and can't be added because the existing type is '"
+ exist.getClass().getName());
}
}
if (exist != null)
{
throw new IllegalArgumentException("model '" + key + "' at '" + keys[i]
+ "' has conflicting updates for '" + exist.toString() + "' and '"
+ target.toString() + "'");
}
registry.put(keys[i], buildDeepTemplateMap(keys, i + 1, model));
}
}
private TemplateModel buildDeepTemplateMap(String[] keys, int start, TemplateModel target)
{
for(int i = keys.length - 1; i >= start; i--)
{
MapDotModel model = new MapDotModel();
model.put(keys[i], target);
target = model;
}
return target;
}
public TemplateModel useObjectModel(Object target) throws TemplateModelException
{
ObjectWrapper wrapper = cfg.getObjectWrapper();
TemplateModel model = wrapper.wrap(target);
return model;
}
public TemplateModel useObjectMethod(Object target, String methodName) throws TemplateModelException
{
TemplateHashModel model = (TemplateHashModel) useObjectModel(target);
return model.get(methodName);
}
public TemplateModel useClass(String className) throws TemplateModelException
{
BeansWrapper wrapper = (BeansWrapper) cfg.getObjectWrapper();
TemplateHashModel staticModels = wrapper.getStaticModels();
return staticModels.get(className);
}
}
然后我们就可以使用它提供的功能去做我们的扩展了:
ModelRegistrar mr = new ModelRegistrar();
mr.registerGlobalModel("global.string", mr.useClass("java.lang.String"));
SimpleDateFormat sdf = new SimpleDateFormat("'Current time is' yyyy-MM-dd hh:mm:ss S");
mr.registerGlobalMethod("global.date.format", sdf, "format");
mr.registerGlobalModel("global.date.currentTime", new Date());
//Freemarker:
${global.string.format("This is the string format string, see the grap. %60s", "arguments")}
${global.date.format(global.date.currentTime)}
//output sample:
This is the string format string, see the grap. arguments
Current time is 2010-08-17 02:19:44 856
最后,还需要提到一些在扩展方面需要注意的地方,并不是对象所有的方法和属性都可以被Freemarker访问得到的,对于static方法和属性来说,如果类注册到model中后,我们可以使用它的名字去调用或者访问它。而对于成员变量来说,如果需要访问它,则必须为它指定一个get方法。另外,有些方法是被标注为unsafe的,例如java.lang.System.exit(), java.lang.Object.wait(), 详细情况参考freemarker.ext.beans.unsafeMethods.txt 和
freemarker.ext.beans.BeansWrapper.