freemarker part2
前一篇简单的介绍了FreeMarker的用法和特性,这一篇介绍一些开发相关的核心组件
快速开始
创建Configuration实例
Configuration是FreeMarker对应应用基本配置的封装,同时它也处理模板的创建和缓存等
// cfg最好是单例,因为cfg创建成本高
Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
// 路径
cfg.setDirectoryForTemplateLoading(new File("/ftl"));
// 编码
cfg.setDefaultEncoding("UTF-8");
// 异常处理
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false);
创建data-model
数据模型即模板渲染所需的数据
// 数据
Map<String,Object> dataModel = Maps.newHashMap();
dataModel.put("user", "FreeMarker");
dataModel.put("product", Product.builder()
.name("testName")
.url("http://test/url")
.build());
获取Template&渲染数据
Template即FreeMarker对模板的封装,通过process方法将渲染后的文件放到由文件流处理
// 获取模板
cfg.getTemplate("test.ftl");
// 渲染
try (Writer writer = new OutputStreamWriter(System.out)){
template.process(dataModel, writer);
}
数据模型
基础TemplateModel
所有Java的变量都会转为TemplateModel。可以理解为FreeMarker会将传进来的数据类型都包装一次,便于统一处理。
标准Scalars
Template{type}Model
Boolean: TemplateBooleanModel
Number: TemplateNumberModel (默认实现为SimpleNumber)
String: TemplateScalarModel(因为历史原因String对应的不叫TemplateStringModel,而是TemplateScalarModel,且默认实现为SimpleScalar)
Date-like: TemplateDateModel(比较麻烦因为Java用不用类来表示日期、时间和日期时间)
容器Containers
hashes、sequences和collections
hashes: TemplateHashModel、TemplateHashModelEx(默认实现SimpleHash)
sequences: TemplateSequenceModel(默认实现SimpleSequence)
collections: TemplateCollectionModel(默认实现SimpleCollection)
方法
同理数据模型的方法FreeMarker也会封装一层TemplateMethodModel、TemplateMethodModelEx
自定义方法实现
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)));
}
}
模型设置
root.put("indexOf", new IndexOfMethod());
使用
<#assign x = "something">
${indexOf("met", x)}
${indexOf("foo", x)}
指令
所有指令由TemplateDirectiveModel封装,用户自定义指令也需要实现TemplateDirectiveModel接口
例子(大写命令范围所有字符)
UpperDirective
public class UpperDirective implements TemplateDirectiveModel {
@Override
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){
throw new RuntimeException("missing body");
}
body.render(new UpperCaseFilterWriter(env.getOut()));
}
private static class UpperCaseFilterWriter extends Writer {
private final Writer out;
public UpperCaseFilterWriter(Writer out){
this.out = out;
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
// 将所有字符转为大写
char[] transformChars = new char[len];
for (int i=0; i<len; i++){
transformChars[i] = Character.toUpperCase(cbuf[i + off]);
}
out.write(transformChars);
}
@Override
public void flush() throws IOException {
out.flush();
}
@Override
public void close() throws IOException {
out.close();
}
}
}
加入DataModel或加入cfg
// 仅此dataModel可用
dataModel.put("upper", new UpperDirective());
// 全局都可用
cfg.setSharedVariable("up", new UpperDirective())
ftl使用
<@upper>
Kkkkk
${user}
Wwwww
</@upper>
节点
节点就是类似树的数据类型。FreeMarker用TemplateNodeModel统一封装。处理XML结构需要用TemplateNodeModel.
常用方法
String getNodeName():节点名称
String getNodeType():节点类型
String getNamespaceURI():资源路径
配置
基础配置
Configuration一般是应用级别的(单例),一个Configuration绑定多个Template。特殊场景也会存在多个Configuration。
共享变量
所有绑定同一Configuration的Template共享的变量。
// 共享变量
cfg.setSharedVariable("up", new UpperDirective());
内置的共享变量
capture_output: freemarker.template.utility.CaptureOutput
compress: freemarker.template.utility.StandardCompress
html_escape: freemarker.template.utility.HtmlEscape
normalize_newlines: freemarker.template.utility.NormalizeNewlines
xml_escape: freemarker.template.utility.XmlEscape
Settings
比如:locale、number_format、default_encoding等,完整的可参考https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#setSetting-java.lang.String-java.lang.String-
cfg.setSetting(...);
模板相关
模板加载
通过文件、数据库或者内存等进行加载至Template
内置加载方法
void setDirectoryForTemplateLoading(File dir): 文件流
void setClassForTemplateLoading(Class cl, String basePackagePath): 类加载
void setServletContextForTemplateLoading(Object servletContext, String path):web资源
也可以自定义TemplateLoader,然后设置到cfg即可
组合加载
可以从多个地方加载模板
FileTemplateLoader ftl1 = new FileTemplateLoader(new File("/tmp/templates"));
FileTemplateLoader ftl2 = new FileTemplateLoader(new File("/usr/data/templates"));
ClassTemplateLoader ctl = new ClassTemplateLoader(getClass(), "/com/example/templates");
MultiTemplateLoader mtl = new MultiTemplateLoader(new TemplateLoader[] { ftl1, ftl2, ctl });
cfg.setTemplateLoader(mtl);
其他加载
如URLTemplateLoader加载等
模板缓存
通过cfg.getTemplate是获取缓存的模板,如果你改变了模板,会在下次获取时生效(默认5s)。
注意有些加载器(基于classloader的)不支持刷新。我们也可以实现TemplateLoader来自定义。
错误处理
FreeMarker在处理模板时可能会发生异常,默认有以下几种处理器。
TemplateExceptionHandler.DEBUG_HANDLER: 打日志然后抛异常
TemplateExceptionHandler.HTML_DEBUG_HANDLER: 同上,不过日志格式为HTML
TemplateExceptionHandler.IGNORE_HANDLER: 忽略异常,不会抛
TemplateExceptionHandler.RETHROW_HANDLER: 重新抛异常
可以实现TemplateExceptionHandler实现自定义错误处理
-- 自定义错误处理器
class MyTemplateExceptionHandler implements TemplateExceptionHandler {
public void handleTemplateException(TemplateException te, Environment env, java.io.Writer out)
throws TemplateException {
try {
out.write("[ERROR: " + te.getMessage() + "]");
} catch (IOException e) {
throw new TemplateException("Failed to print error message. Cause: " + e, env);
}
}
}
-- 配置错误处理器
cfg.setTemplateExceptionHandler(new MyTemplateExceptionHandler());
模板配置
TemplateConfiguration tcUTF8XML = new TemplateConfiguration();
tc.setEncoding("utf-8");
tc.setOutputFormat(XMLOutputFormat.INSTANCE);
cfg.setTemplateConfigurations(
new ConditionalTemplateConfigurationFactory(
new FileExtensionMatcher("xml"),
tcUTF8XML));
其他
整合Servlet
见https://freemarker.apache.org/docs/pgui_misc_servlet.html