FreeMarker 是一个基于 Java 的模板引擎,广泛用于动态内容生成,尤其适用于 Web 应用程序中的视图层。它通过将业务逻辑与表示逻辑分离,提升了开发效率和代码可维护性。
一、核心概念
1.1 模板(Template)
- FreeMarker 模板是文本文件(如 HTML、XML、JSON 等),包含静态内容和 FreeMarker 指令。
- 使用
.ftl
作为文件扩展名。
1.2 数据模型(Data Model)
- 一个 Java 对象或 Map,包含模板中使用的变量。
- FreeMarker 通过反射机制访问这些变量。
1.3 指令(Directives)
- 控制结构(如
if
,list
,assign
)和宏(macro)等。 - 以
<#...>
形式出现。
1.4 指令类型
- 控制指令:
<#if>
,<#list>
,<#switch>
- 赋值指令:
<#assign>
,<#local>
- 宏与函数:
<#macro>
,<#function>
- 导入与包含:
<#include>
,<#import>
- 空处理:
??
,!
,??
1.5 模板加载与缓存
- FreeMarker 支持从文件系统、类路径、数据库等加载模板。
- 支持模板缓存机制,提升性能。
二、操作步骤(详细)
2.1 引入依赖
Maven 项目:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version> <!-- 使用最新稳定版本 -->
</dependency>
Gradle 项目:
implementation 'org.freemarker:freemarker:2.3.32'
2.2 创建模板文件(如:hello.ftl
)
<html>
<head><title>FreeMarker Example</title></head>
<body>
<h1>Hello, ${name}!</h1>
<ul>
<#list items as item>
<li>${item}</li>
</#list>
</ul>
</body>
</html>
保存路径如:src/main/resources/templates/hello.ftl
2.3 配置 FreeMarker 配置对象(Configuration
)
import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
public class FreeMarkerConfig {
public static Configuration getConfiguration() throws Exception {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
// 设置模板路径(文件系统路径)
// cfg.setDirectoryForTemplateLoading(new File("/path/to/templates"));
// 或者从 classpath 加载模板
cfg.setClassForTemplateLoading(FreeMarkerConfig.class, "/templates");
// 设置默认编码
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false);
cfg.setWrapUncheckedExceptions(true);
return cfg;
}
}
2.4 准备数据模型
import java.util.HashMap;
import java.util.Map;
public class DataModel {
public static Map<String, Object> getModel() {
Map<String, Object> model = new HashMap<>();
model.put("name", "John Doe");
model.put("items", List.of("Apple", "Banana", "Cherry"));
return model;
}
}
2.5 渲染模板
import freemarker.template.Template;
import freemarker.template.Configuration;
import java.io.StringWriter;
import java.util.Map;
public class TemplateRenderer {
public static String renderTemplate(String templateName, Map<String, Object> model) throws Exception {
Configuration cfg = FreeMarkerConfig.getConfiguration();
Template template = cfg.getTemplate(templateName);
StringWriter writer = new StringWriter();
template.process(model, writer);
return writer.toString();
}
}
2.6 主程序调用示例
public class MainApp {
public static void main(String[] args) {
try {
Map<String, Object> model = DataModel.getModel();
String html = TemplateRenderer.renderTemplate("hello.ftl", model);
System.out.println(html);
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、常见错误与解决方法
错误类型 | 描述 | 解决方法 |
---|---|---|
TemplateNotFoundException |
找不到模板文件 | 检查模板路径是否正确,确保模板在 classpath 或指定目录 |
InvalidReferenceException |
变量未定义 | 检查模板中变量名是否与模型一致,使用 ?? 判断是否存在 |
MalformedTemplateNameException |
模板名格式错误 | 检查模板名称是否包含非法字符 |
ParseException |
模板语法错误 | 检查 FreeMarker 指令语法是否正确 |
TemplateException |
模板执行错误 | 查看堆栈信息,定位具体行号 |
四、注意事项
- 模板路径:确保模板路径正确,使用绝对路径或 classpath 路径。
- 编码问题:模板和输出内容都应使用 UTF-8 编码。
- 线程安全:
Configuration
是线程安全的,可以全局单例使用。 - 异常处理:建议设置
TemplateExceptionHandler
来捕获模板异常。 - 空值处理:使用
${variable!}
或<#if variable??>
避免空指针异常。
五、使用技巧
- 使用
<#include>
和<#import>
:复用模板片段,提升可维护性。 - 定义宏(Macro):
<#macro greet name>
<p>Hello, ${name}!</p>
</#macro>
<@greet name="Alice"/>
- 使用内建函数:
${"hello world"?cap_first} <!-- 输出 Hello world -->
${"hello world"?upper_case} <!-- 输出 HELLO WORLD -->
- 条件判断:
<#if user.isAdmin>
<p>Welcome, Admin!</p>
</#if>
- 集合遍历:
<#list users as user>
<p>${user.name}</p>
</#list>
六、最佳实践
- 模板命名规范:如
user_profile.ftl
,清晰表达用途。 - 模板与逻辑分离:避免在模板中写 Java 代码。
- 使用缓存机制:开启模板缓存提高性能。
- 模板安全:避免在模板中执行危险操作。
- 日志记录:记录模板渲染过程中的异常信息。
- 模板预编译:在构建阶段预编译模板(如使用 Maven 插件)。
七、性能优化
7.1 启用模板缓存(默认已启用)
cfg.setTemplateCacheSize(100); // 设置缓存大小
cfg.setCacheStorage(new freemarker.cache.MruCacheStorage(100, 100)); // 使用 LRU 缓存策略
7.2 避免频繁创建 Configuration
- 将
Configuration
实例作为单例使用。
7.3 使用 StringWriter
而非 FileWriter
进行内存渲染
- 避免频繁 IO 操作,提升性能。
7.4 使用 ?if_exists
避免模板加载失败
<#include "header.ftl" if_exists>
7.5 使用 ?default
处理空值
${user.name?default("Guest")}
八、总结
项目 | 内容 |
---|---|
核心组件 | Configuration , Template , DataModel |
常见错误 | 路径错误、变量未定义、语法错误 |
最佳实践 | 单例配置、模板复用、空值处理 |
性能优化 | 模板缓存、避免重复创建、内存渲染 |