Freemarker 高级进阶

博学,切问,近思--詹子知 (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.

时间: 2024-10-24 14:59:22

Freemarker 高级进阶的相关文章

SCO UNIX高级进阶(一)

第七节高级进阶 1.问题:我改了 hosts 文件,譬如: 127.0.0.1 localhost改成: 127.0.0.1 local 不重起系统,怎样让它生效? 答:# tcp stop # tcp start 建议:好多地方要用到本机的反馈地址的,建议不要修改!!! 2.问题:我想修改主机名,如何修改? 答:如果想修改主机名,用uname -S 主机名. 3.问题:怎么看我的SCO是多少用户? 答:uname -X 在liences manager中也可以看到!! uname -A 更好

《高级进阶DB2(第2版)——内部结构、高级管理与问题诊断》之我见

<高级进阶DB2(第2版)--内部结构.高级管理与问题诊断>之我见 从IT开发与运维角度来分析,千千万万的业务应用系统,最核心的最有价值的是业务数据:而这些多年来积累与沉淀下来的数据依托于数据库系统,数据库系统是否稳定与性能高低则是考验数据库内核,而作为核心之重的数据库内核则是各种商用与开源数据库服务器软件实现与关注的重点所在. 从数据库服务器软件的市场来看,DB2所占据的地位与份额真是犹如乒乓球界的王晧与羽毛球界的李宗伟,长期占居千年老二之位,位亦如其名啊(*_*) 作为要立志成为资深的DB

C++高级进阶 第一季:const 详解

零.文章来由 打算将基础知识在看书的同时系统的整理一下,方便大家也方便自己.整理的知识尽量参照书本知识,比网上获取的资料有更高的可信度. 一.从 文字常量和常变量 开始 补充:const并没有想象中的那么简单,详见新博文<C++底层知识 第三季:const详解(二)> 1.文字常量 程序中的特殊标识符或表达式,由于同时满足: (1)不可寻址(放在代码区) (2)值不可变 所以可视为文字常量.他们是 静态数组名.枚举变量.全局(静态变量)首地址.#define定义的常量. 整型文字常量: (1)

C++高级进阶 第二季:mutable 关键字

零.文章来由 打算将基础知识在看书的同时系统的整理一下,方便大家也方便自己.整理的知识尽量参照书本知识,比网上获取的资料有更高的可信度. 一.作用 mutable 用来解决常函数中不能修改对象的数据成员的问题. 如果在一些情况下,希望在常函数中仍然可以修改某个成员变量的值,就在该变量前加上mutable.能在保证常量对象大部分数据成员仍然"只读"情况下,实现对个别成员的修改. #include <iostream> #include <string> using

C#可扩展编程之MEF学习笔记(五):MEF高级进阶

好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用的基本已经讲完了,相信大家已经能看出MEF所带来的便利了.今天就介绍一些MEF中一些较为不常用的东西,也就是大家口中的所谓的比较高级的用法. 前面讲的导出都是在每个类上面添加Export注解,实现导出的,那么有没有一种比较简便的方法呢?答案是有的,就是在接口上面写注解,这样只要实现了这个接口的类都会

SCO UNIX高级进阶(二)

11.问题:如何启动时自动添加网关? 答:可以在/etc/rc2.d目录下用vi创建一个名为S10route的文件,内容为: route add default xxx.xxx.xxx.xxx 2 其中xxx.xxx.xxx.xxx为网关. 答:在vi /etc/tcp 找到route 那行修改一下岂不更为简单?在ifconfig后. 在/etc/tcp文件里面添加也可以. 答:在/etc/rc中更方便 答:SCO UNIX环境下自动增加网关的两种方法: 1.方法一:编辑产生一个/etc目录下的

SCO UNIX高级进阶(三)

22.问题:sco unix5.06如何配置DNS? 答:vi /etc/resolv.conf 内容为:nameserver xxx.xxx.xxx.xxx.xxx.xxx.xxx.xxx就是实际的DNS. 23.问题:NetScape如何使用? 答:1.加网关 以超级用户登录,并键入以下命令: route add default 192.168.0.1 (IP 地址为网关地址) 同时可以用vi /etc/rc2.d/S99route建立文件,使机器开机后会自动加载网关 文件内容为: rout

C++高级进阶 第三季:求余运算符+运算符结合律

一.求余运算符 %用于求余数,优先级与*和/相同,结合律也是从左至右. 要求两个操作数均为整数(或可以隐式转换成整数的类型),故:14.2%3就是错误的,因为double不能隐士转换为整形. #include <iostream> using namespace std; int main() { char c=253; int i=5; cout<<c%2<<endl; cout<<i%c<<endl; cout<<19%10%5&

C++高级进阶 第四季:const详解(二) 常量折叠

一.文章来由 const详解之二 二.const 代替 #define const最初动机就是代替 #define. const 优于 #define: (1) #define没有类型检查,const在编译期(而不是预编译期)做类型检查: (2)const方便调试和定位bug. 所以应该完全用const代替#define 三.头文件中的const (1)要使用const代替#define,同样需要把const定义放进头文件(或其他格式文件,include即可).这样通过包含头文件,可把const