FreeMarker开发-数据模型
FreeMarker用于处理模板的数据模型是哈希表,也就是一个树状结构的name-value对。如下:
(root)
|
+- string="string"
|
+- map
| |
| +- map1 = "map1"
| |
| +- map2 = "map2"
|
+- object
| |
| +- field1= "field1"
| |
| +- field2 = "field1"
| |
| +- method= "method"
|
......
在Java代码中(下面第15行),我们提供给模板引擎的数据(process方法的第一个参数),可以是Map,也可以是自定义的Java对象。但是,模板引擎在处理时,并不是直接使用我们提供的类型。它会将其转换为自己内部定义的类型,转换工作由第8行的ObjectWrapper去完成,这种特性被称作“对象包装(Object Swapping)”。
查看源码,关于Template#process(Object dataModel, Writer out)方法中dataModel参数的解释如下:
dataModel是从模板中可以获取的变量的容器(name-value对);
dataModel通常是一个Map<String, Object>或者是一个JavaBean(JavaBean的属性将成为变量);
可以是BeanWrapper在执行时可以转换为TemplateHashModel的任意对象;
也可以是实现了TemplateHashModel接口的对象,这种情况在执行时将直接使用,不再进行包装;
如果是null,则使用一个空的数据模型。

1 public class FreeMarkerTest {
2 public static void main(String[] args) throws IOException,
3 TemplateException {
4 Configuration conf = new Configuration();
5 // 设置加载模板文件的目录
6 conf.setDirectoryForTemplateLoading(new File("src/templates"));
7 // 设置模板检索数据模型的方式
8 conf.setObjectWrapper(new DefaultObjectWrapper());
9 // 创建、解析模板并缓存
10 Template template = conf.getTemplate("example.flt");
11 // 准备数据
12 Map<String, Object> root = new HashMap<String, Object>();
13 root.put("example", "Hello World!");
14 // 将数据与模板合并
15 template.process(root, new OutputStreamWriter(System.out));
16 }
17 }

一、对象包装(Object Swapping)
FreeMarker数据模型的哈希表中的name为字符串,value可以为标量、容器、子程序和节点等。这也是FreeMarker内部使用的变量的类型。这些类型都实现了freemarker.template.TemplateModel接口。
1. 标量:包括数值、字符串、日期、布尔。
2. 容器:包括哈希表(Map,类似于Java中的Map)、序列(Sequence,类似于Java中的数组和有序集合)、集合(Collection,类似于Java中的集合)。
3. 子程序:包括方法(Method)和指令(Directive)。
4. 节点:主要是为了帮助用户在数据模型中处理XML文档。
在模板处理时,会将Java类型包装为对应的TemplateModel实现。比如将一个String包装为SimpleScalar来存储同样的值。对于每个Java类型,具体选择什么TemplateModel实现去包装,取决于对象包装器(ObjectWrapper)的实现策略,可通过上面代码第8行设置。ObjectWrapper是一个接口,FreeMarker核心包提供了两个基本的实现:ObjectWrapper.DEFAULT_WRAPPER、ObjectWrapper.BEANS_WRAPPER。
1. ObjectWrapper.DEFAULT_WRAPPER: 按照下表对应关系包装,Jython类型包装为freemarker.ext.jython.JythonWrapper,其它类型调用BEANS_WRAPPER。
2. ObjectWrapper.BEANS_WRAPPER:利用Java反射来获取对象的成员属性。
类型 | FreeMarker接口 | FreeMarker实现 |
字符串 | TemplateScalarModel | SimpleScalar |
数值 | TemplateNumberModel | SimpleNumber |
日期 | TemplateDateModel | SimpleDate |
布尔 | TemplateBooleanModel | TemplateBooleanModel.TRUE |
哈希 | TemplateHashModel | SimpleHash |
序列 | TemplateSequenceModel | SimpleSequence |
集合 | TemplateCollectionModel | SimpleCollection |
节点 | TemplateNodeModel | NodeModel |
ObjectWrapper的这两个属性现已经被标注为@deprecated,并建议用DefaultObjectWrapperBuilder#build()和BeansWrapperBuilder#build()方式获取实例。如下:
// conf.setObjectWrapper(new DefaultObjectWrapperBuilder(new Version("2.3.22")).build());
conf.setObjectWrapper(new BeansWrapperBuilder(new Version("2.3.22")).build());
二、自定义方法
自定义方法需要实现freemarker.template.TemplateMethodModel接口(当前已@deprecated),建议替换为TemplateMethodModelEx。
例:实现累加方法sum(int...num)。参数可以有多个整数。
模板如下:
${sum(1, 2, 3, 4)}
代码如下:

package org.genein.freemark.templateModel; import java.util.List; import freemarker.template.SimpleNumber;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException; public class SumMethod implements TemplateMethodModelEx { @SuppressWarnings("rawtypes")
@Override
public Object exec(List arg0) throws TemplateModelException {
if (arg0 == null || arg0.size() == 0) {
return new SimpleNumber(0);
} double sum = 0l;
double tmp;
for (int i = 0; i < arg0.size(); i++) {
tmp = Double.valueOf(arg0.get(i).toString());
sum += tmp;
}
return new SimpleNumber(sum);
}
}

FreeMarkerTest.main方法添加以下代码:
1 // 添加方法工具
2 root.put("sum", new SumMethod());
3 // 将数据与模板合并
4 template.process(root, new OutputStreamWriter(System.out));
输出如下:
10
三、自定义指令
自定义指令需要实现freemarker.template.TemplateDirectiveModel接口。
例:自定义一个指令,循环输出内嵌内容,count参数决定循环次数(必填),hr参数决定是否添加分隔符“<hr>”(可选,默认false),step参数表示当前循环次数(可选)。
模板如下:
<@repeat count=5 hr=false; step>
${step}. ${name}
</@repeat>
代码如下:

package org.genein.freemark.templateModel; import java.io.IOException;
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; public class RepeatDirective implements TemplateDirectiveModel {
/**
* 循环次数
*/
private static final String COUNT = "count";
/**
* 是否需要用hr标签间隔
*/
private static final String HR = "hr"; @SuppressWarnings("rawtypes")
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
// 获取count参数,并校验是否合法
TemplateModel countModel = (TemplateModel) params.get(COUNT);
if (countModel == null) {
throw new TemplateModelException("缺少必须参数count!");
}
if (!(countModel instanceof TemplateNumberModel)) {
throw new TemplateModelException("count参数必须为数值型!");
}
int count = ((TemplateNumberModel) countModel).getAsNumber().intValue();
if (count < 0) {
throw new TemplateModelException("count参数值必须为正整数!");
} // 获取hr参数,并校验是否合法
boolean hr = false;
TemplateModel hrModel = (TemplateModel) params.get(HR);
if (hrModel != null) {
if (!(hrModel instanceof TemplateBooleanModel)) {
throw new TemplateModelException("hr参数值必须为布尔型!");
}
hr = ((TemplateBooleanModel) hrModel).getAsBoolean();
} // 检验内嵌内容是否为空
if (body == null) {
throw new RuntimeException("内嵌内容不能为空!");
} // 最多只允许一个循环变量
if (loopVars.length > 1) {
throw new TemplateModelException("最多只允许一个循环变量!");
} // 循环渲染内嵌内容
for (int i = 0; i < count; i++) {
// 用第一个循环变量记录循环次数
if (loopVars.length == 1) {
loopVars[0] = new SimpleNumber(i + 1);
}
// 上面设置循环变量的操作必须在该render前面,因为内嵌内容中使用到了该循环变量。
// body.render(new UpperCaseFilterWriter(env.getOut()));
body.render(env.getOut());
if (hr) {
env.getOut().write("<hr>");
}
}
}
}

FreeMarkerTest.main增加以下代码:
root.put("name", "Genein");
// 添加自定义指令
root.put("repeat", new RepeatDirective());
// 将数据与模板合并
template.process(root, new OutputStreamWriter(System.out));
输出如下:
1. Genein
2. Genein
3. Genein
4. Genein
5. Genein
FreeMarker开发-数据模型的更多相关文章
- SpringBoot+FreeMarker开发word文档下载,预览
背景: 开发一个根据模版,自动填充用户数据并下载word文档的功能 使用freemarker进行定义模版,然后把数据进行填充. maven依赖: <parent> <groupId& ...
- Freemarker 程序开发
Freemarker 程序开发 现在web开发中,多使用freemarker 来描述页面.通常会使用的macro来定义各种组件,从而达到UI组件的复用.结合使用其它的指定,可快速的描述一个html页面 ...
- freeMarker(十三)——XML处理指南之揭示XML文档
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net 前言 尽管 FreeMarker 最初被设计用作Web页面的模板引擎, ...
- freemaker开发
推荐书籍 百度云盘 密码: c3m9 1. 前言 本书为<FreeMarker 2.3.19 中文版手册>,包含了freemarker开发得方方面面,可以作为开发freemarker的字典 ...
- SpringBoot2 整合FreeMarker模板,完成页面静态化处理
本文源码:GitHub·点这里 || GitEE·点这里 一.页面静态化 1.动静态页面 静态页面 即静态网页,指已经装载好内容HTML页面,无需经过请求服务器数据和编译过程,直接加载到客户浏览器上显 ...
- IOS开发官方文档随笔
马上着手开发IOS应用程序 创建第一个单视图应用 ###main 方法 int main(int argc, char * argv[]) { @autoreleasepool { return UI ...
- FreeMarker教程
一.什么是模板引擎,为什么要用模板引擎 在B/S程式设计中,常常有美工和程序员二个角色,他们具有不同专业技能:美工专注于表现——创建页面.风格.布局.效果等等可视元素:而程序员则忙于创建程式的商业流程 ...
- 巧用Freemarker的自定义方法
要想使用Freemarker支持的自定义方法,需要实现freemarker.template.TemplateMethodModel接口,然后将方法对象放入到Freemarker的数据模型中,这样在f ...
- Freemarker自定义方法
在项目中有一个需求,每个物品有一个guid,存在数据库中,而在页面上需要显示一个对应的业务数据值,暂且叫做serverId,serverId是通过guid移位计算得来.serverId只需要显示,后台 ...
随机推荐
- spring容器启动完成后加载自定义逻辑
业务需求中,可能会有一些逻辑需要在应用启动完成后,例如字典缓存,资源池初始化等等,代码如下 public class InitApplication implements ApplicationCon ...
- 017-zabbix_proxy分布式监控部署
一.proxy分布式监控介绍 来源于zabbix官网: https://www.zabbix.com/documentation/3.4/zh/manual/distributed_monitorin ...
- mysql时间函数操作
Mysql时间转换函数 https://blog.csdn.net/w_qqqqq/article/details/88863269 mysql时间日期函数 https://www.cnblogs.c ...
- 【洛谷P1417】烹调方案 贪心+背包dp
题目大意:一共有 n 件食材,每件食材有三个属性,ai,bi和ci,如果在t时刻完成第i样食材则得到ai-t*bi的美味指数,用第i件食材做饭要花去ci的时间.众所周知,gw的厨艺不怎么样,所以他需要 ...
- NOIP2016提高A组五校联考4总结
坑爹的第一题,我居然想了足足3个小时,而且还不确定是否正确. 于是,我就在这种情况下心惊胆跳的打了,好在ac了,否则就爆零了. 第二题,树形dp,本来差点就想到了正解,结果时间不够,没打完. 第三题, ...
- 工作笔记--js-点赞按钮和踩踩按钮互斥??怎么写?
效果图: html: css: .an{ margin-top:0px; position: relative; .popzframe,.popcframe{ display: none; word- ...
- python 从入门到实践 第三章
在第3章,你将学习如何在被称为列表的变量中存储信息集,以及如何通过遍历列表来操作其中的信息 写注释 # 代码越长 标识好代码的重要性 越来越重要要求习惯:在代码中编写清晰,简洁的注释开始研究更复杂的主 ...
- 面向对象this关键字的内存图解
this:哪个对象调用方法,this就代表哪个对象 案例1: //定义老师类 class Teacher { private String name; private int age; public ...
- [模板][HDU]P2544[单源最短路][SPFA]
题目就不放了,主要是写一下SPFA,很少写,今天特别学了一个用STL的队列来做的. 代码: #include<iostream> #include<cstdio> #inclu ...
- 157. [USACO Nov07] 奶牛跨栏(第三次考试大整理)
157. [USACO Nov07] 奶牛跨栏 输入文件:hurdles.in 输出文件:hurdles.out 简单对比 时间限制:1 s 内存限制:128 MB 译 by CmYkRg ...