一. 创建Configuration实例
1. 使用FreeMarker, 首先, 你应该创建一个freemarker.template.Configuration实例, 然后调整它的设置。Configuration实例是存储FreeMarker应用级设置的核心部分。同时, 它也处理创建和缓存预解析模板(比如: Template对象)的工作。
2. 不需要重复创建Configuration实例; 它的代价很高, 尤其是会丢失缓存。Configuration实例就是应用级别的单例。
3. 当使用多线程应用程序(比如Web网站), Configuration实例中的设置就不能被修改。它们可以被视作为"有效的不可改变的"对象, 也可以使用线程安全技术来保证实例对其它线程也可用。
二. 创建数据模型
1. 在简单的示例中你可以使用java.lang和java.util包中的类, 还有用户自定义的Java Bean来构建数据对象:
1.1. 使用java.lang.String来构建字符串。
1.2. 使用java.lang.Number来派生数字类型。
1.3. 使用java.lang.Boolean来构建布尔值。
1.4. 使用java.util.List或Java数组来构建序列。
1.5. 使用java.util.Map来构建哈希表。
1.6. 使用自定义的bean类来构建哈希表, bean中的项和bean的属性对应。比如: product的price属性(getProperty())可以通过product.price获取。
2. 下面是构建这个数据模型的Java代码片段:
Map<String, Object> root = new HashMap<String, Object>();
List<String> colors = new ArrayList<String>();
colors.add("陶瓷黑");
colors.add("陶瓷白");
root.put("colors", colors);
root.put("id", 100001);
root.put("name", "HUAWEI Mate 40 RS 保时捷设计");
root.put("price", 12999.00F);
root.put("date", new Date(System.currentTimeMillis()));
root.put("fastCharge", true);
3. 在真实应用系统中, 通常会使用应用程序指定的类来代替Map, 它会有JavaBean规范规定的getXxx/isXxx方法。比如有一个和下面类似的类:
public class Product {
private Integer id; // 商品id
...
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
...
}
List<String> colors = new ArrayList<String>();
colors.add("陶瓷黑");
colors.add("陶瓷白");
Product root = new Product(100002, "HUAWEI Mate 40 RS 保时捷设计", 12999.00F, colors, new Date(System.currentTimeMillis()), true);
三. 获取模板
1. 模板代表了freemarker.template.Template实例。典型的做法是从Configuration实例中获取一个Template实例。无论什么时候你需要一个模板实例, 都可以使用它的getTemplate方法来获取。假如我们获取一个test.html模板, 那么就可以这样来做:
Template temp = cfg.getTemplate("test.html");
2. 当调用这个方法的时候, 将会创建一个test.html的Template实例, 通过读取/where/you/store/templates/test.html文件, 之后解析(编译)它。Template实例以解析后的形式存储模板, 而不是以源文件的文本形式。
3. Configuration缓存Template实例, 当再次获得test.html的时候, 它可能再读取和解析模板文件了, 而只是返回第一次的Template实例。
四. 合并模板和数据模型
1. 我们已经知道, 数据模型+模板=输出, 我们有了一个数据模型(root)和一个模板(temp), 为了得到输出就需要合并它们。这是由模板的process方法完成的。它用数据模型root和Writer对象作为参数, 然后向Writer对象写入产生的内容。为简单起见, 这里我们只做标准的输出:
Writer out = new OutputStreamWriter(System.out);
temp.process(root, out);
2. Java I/O相关注意事项: 基于out对象, 必须保证out.close()最后被调用。其它时候, 比如典型的Web应用程序, 那就不能关闭out对象。FreeMarker会在模板执行成功后(也可以在Configuration中禁用)调用out.flush(), 所以不必为此担心。
3. 请注意, 一旦获得了Template实例, 就能将它和不同的数据模型进行不限次数(Template实例是无状态的)的合并。此外, 当Template实例创建之后test.html文件才能访问, 而不是在调用处理方法时。
5. 例子
5.1. 新建一个名为FMRuMen动态Web工程, 同时添加相关jar包。
5.2. 编写FMFactory.java
package com.fm.util;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
public class FMFactory {
private final static FMFactory instance = new FMFactory();
private FMFactory() {}
public static FMFactory getInstance() {
return instance;
}
private Map<String, Configuration> map = new ConcurrentHashMap<String, Configuration>();
// 创建单个Configuration实例
public synchronized Configuration getCfg(Object servletContext, String path) {
if(null != map.get(path)) {
return map.get(path);
}
Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
cfg.setServletContextForTemplateLoading(servletContext, path);
cfg.setDefaultEncoding("utf-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
map.put(path, cfg);
return cfg;
}
}
5.3. 编写Product.java
package com.fm.model;
import java.io.Serializable;
import java.sql.Date;
import java.util.List;
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id; // 商品id
private String name; // 商品名称
private Float price; // 商品价格
private List<String> colors; // 商品颜色
private Date date; // 出厂日期
private Boolean fastCharge; // 是否支持快充
public Product() {
}
public Product(Integer id, String name, Float price, List<String> colors, Date date, Boolean fastCharge) {
this.id = id;
this.name = name;
this.price = price;
this.colors = colors;
this.date = date;
this.fastCharge = fastCharge;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
public List<String> getColors() {
return colors;
}
public void setColors(List<String> colors) {
this.colors = colors;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Boolean getFastCharge() {
return fastCharge;
}
public void setFastCharge(Boolean fastCharge) {
this.fastCharge = fastCharge;
}
}
5.4. 编写ProductInfo.java
package com.fm.action;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.sql.Date;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fm.model.Product;
import com.fm.util.FMFactory;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
public class ProductInfo extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Configuration cfg = FMFactory.getInstance().getCfg(req.getServletContext(), "WEB-INF/templates/product");
// Map<String, Object> root = new HashMap<>();
// List<String> colors = new ArrayList<String>();
// colors.add("陶瓷黑");
// colors.add("陶瓷白");
// root.put("colors", colors);
// root.put("id", 100001);
// root.put("name", "HUAWEI Mate 40 RS 保时捷设计");
// root.put("price", 12999.00F);
// root.put("date", new Date(System.currentTimeMillis()));
// root.put("fastCharge", true);
// 创建数据模型
List<String> colors = new ArrayList<String>();
colors.add("陶瓷黑");
colors.add("陶瓷白");
Product root = new Product(100002, "HUAWEI Mate 40 RS 保时捷设计", 12999.00F, colors, new Date(System.currentTimeMillis()), true);
// 获取模板
Template temp = cfg.getTemplate("product.html");
Writer out = new OutputStreamWriter(resp.getOutputStream());
try {
// 合并模板和数据模型
temp.process(root, out);
} catch (TemplateException e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
5.5. 编写index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>主页</title>
</head>
<body>
<a href="pi.action">商品信息</a>
</body>
</html>
5.6. 修改web.xml
5.7. 在WEB-INF/templates/product目录下, 编写product.html模板文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>商品信息</title>
</head>
<body>
<!-- c(当用于布尔值时)该内建函数将布尔值转换为字符串, 针对"计算机语言"而不是用户。结果是"true"或"false"。c就代表计算机的意思。-->
<!-- c(当被用作是数字值时)该内建函数将"计算机语言"的数字转换成字符串, 这都是对计算机来说的, 而不是对用户。c就代表计算机的意思。 -->
商品编号: ${id?c} <br />
商品名字: ${name} <br />
商品价格: ¥ ${price?c} <br />
商品颜色: ${colors[0]}, ${colors[1]} <br />
出厂日期: ${date} <br />
<!-- 布尔类型需要转换为字符串类型输出 -->
<!-- booleanExp?then(whenTrue, whenFalse), 就像是类C语言中的三元运算符 -->
支持快充: ${fastCharge?then("支持快充", "不支持快充")}
</body>
</html>
5.8. 运行项目
5.9. 访问商品信息