版本兼容性说明:本文基于 FreeMarker 2.3.x(主流稳定版本),适用于 Java 8+ 环境。
一、核心概念
1. 什么是 TemplateLoader
?
TemplateLoader
是 FreeMarker 框架中用于加载模板文件的核心接口。它定义了如何从各种数据源(如文件系统、类路径、数据库、网络等)读取模板内容。
public interface TemplateLoader {
Object findTemplateSource(String name) throws IOException;
long getLastModified(Object templateSource);
Reader getReader(Object templateSource, String encoding) throws IOException;
void closeTemplateSource(Object templateSource) throws IOException;
}
2. 常见的 TemplateLoader
实现类
实现类 | 来源 | 用途 |
---|---|---|
FileTemplateLoader |
FreeMarker 内置 | 从文件系统加载模板 |
ClassTemplateLoader |
FreeMarker 内置 | 从 classpath 加载模板(最常用) |
StringTemplateLoader |
FreeMarker 内置 | 从内存字符串加载模板(动态模板) |
WebappTemplateLoader |
FreeMarker Web 模块 | 从 Web 应用目录加载(如 /WEB-INF/templates ) |
自定义实现 | 开发者自定义 | 从数据库、Redis、HTTP 等加载模板 |
3. TemplateLoader
与 Configuration
的关系
Configuration
是 FreeMarker 的核心配置类。TemplateLoader
必须通过Configuration.setTemplateLoader()
注册。- 可以设置多个
TemplateLoader
,形成模板加载链(通过MultiTemplateLoader
)。
二、操作步骤(非常详细)
步骤 1:引入依赖(Maven)
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
步骤 2:创建 Configuration
实例
import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
步骤 3:选择并配置 TemplateLoader
✅ 方式一:从 classpath 加载(推荐)
// 从 classpath 的 /templates 目录加载
TemplateLoader loader = new ClassTemplateLoader(
YourClass.class.getClassLoader(),
"/templates"
);
cfg.setTemplateLoader(loader);
示例:模板路径为
src/main/resources/templates/hello.ftl
✅ 方式二:从文件系统加载
File templateDir = new File("/opt/app/templates");
TemplateLoader loader = new FileTemplateLoader(templateDir);
cfg.setTemplateLoader(loader);
✅ 方式三:从字符串动态加载(适合动态模板)
StringTemplateLoader stringLoader = new StringTemplateLoader();
stringLoader.putTemplate("dynamic.ftl", "<h1>Hello ${name}!</h1>");
cfg.setTemplateLoader(stringLoader);
✅ 方式四:组合多个加载器(MultiTemplateLoader)
TemplateLoader[] loaders = {
new ClassTemplateLoader(getClass().getClassLoader(), "/templates"),
new FileTemplateLoader(new File("/custom/templates"))
};
MultiTemplateLoader multiLoader = new MultiTemplateLoader(loaders);
cfg.setTemplateLoader(multiLoader);
加载顺序:按数组顺序查找,找到即返回。
步骤 4:设置其他配置(可选但推荐)
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false);
cfg.setWrapUncheckedExceptions(true);
cfg.setFallbackOnNullLoopVariable(false);
步骤 5:获取并处理模板
try {
// 加载模板(名称相对于 loader 路径)
Template template = cfg.getTemplate("hello.ftl");
// 准备数据模型
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("name", "Alice");
// 输出到 Writer
StringWriter out = new StringWriter();
template.process(dataModel, out);
System.out.println(out.toString());
} catch (IOException | TemplateException e) {
e.printStackTrace();
}
三、常见错误与解决方案
错误现象 | 原因 | 解决方案 |
---|---|---|
TemplateNotFoundException |
模板路径错误或 TemplateLoader 配置错误 |
检查路径是否正确,是否在 classpath 或文件系统中存在 |
MalformedTemplateNameException |
模板名包含非法字符(如 .. ) |
使用 cfg.setSanitizeTemplateNames(true); 启用清理 |
EncodingException |
文件编码与设置不符 | 确保 setDefaultEncoding 与模板文件编码一致(通常 UTF-8) |
NullPointerException in loader |
TemplateLoader 未正确初始化 |
检查 new 是否成功,路径是否存在 |
Access is denied |
文件系统权限不足 | 检查运行用户对模板目录的读权限 |
四、注意事项
路径分隔符:
- 所有路径使用
/
,即使在 Windows 上。 - 不要以
/
开头(除非 loader 明确要求)。
- 所有路径使用
classpath 路径:
ClassTemplateLoader
的路径是相对于 classpath 根的。- 如果模板在
src/main/resources/templates/
,则 loader 路径应为/templates
。
编码一致性:
- 模板文件保存为 UTF-8。
cfg.setDefaultEncoding("UTF-8")
必须设置。
线程安全:
Configuration
是线程安全的,可全局单例。Template
实例也是线程安全的,可缓存复用。
资源释放:
TemplateLoader.closeTemplateSource()
通常由 FreeMarker 自动调用,无需手动处理。
五、使用技巧
1. 动态模板热更新(开发环境)
cfg.setTemplateUpdateDelayMilliseconds(1000); // 每秒检查更新
生产环境应设为较大值或关闭(如
Long.MAX_VALUE
)。
2. 自定义 TemplateLoader
(从数据库加载)
public class DBTemplateLoader implements TemplateLoader {
private final Map<String, String> templateCache = new ConcurrentHashMap<>();
@Override
public Object findTemplateSource(String name) {
return templateCache.get(name); // 返回模板内容字符串
}
@Override
public long getLastModified(Object templateSource) {
return System.currentTimeMillis(); // 或从数据库获取时间戳
}
@Override
public Reader getReader(Object templateSource, String encoding) {
return new StringReader((String) templateSource);
}
@Override
public void closeTemplateSource(Object templateSource) {}
// 从数据库加载模板到 cache
public void loadFromDB() {
// SELECT name, content FROM templates
// templateCache.put(name, content);
}
}
3. 模板缓存控制
cfg.setTemplateLookupStrategy(
new TemplateLookupStrategy() {
@Override
public TemplateLookupResult lookup(TemplateLookupContext context) {
// 可自定义缓存逻辑,如跳过缓存
return context.createFoundTemplateResult(
context.getConfiguration().getTemplate(context.getTemplateName())
);
}
}
);
六、最佳实践
- 使用
ClassTemplateLoader
作为默认加载器(适合大多数应用)。 - 模板路径统一管理:定义常量或配置文件管理模板名。
- 避免频繁创建
Configuration
:使用单例模式。 - 模板命名规范:
.ftl
后缀,语义化命名(如user/profile.ftl
)。 - 开发 vs 生产环境区分:
- 开发:开启模板自动刷新。
- 生产:关闭刷新,提升性能。
七、性能优化
优化项 | 说明 |
---|---|
启用模板缓存 | FreeMarker 默认启用,Configuration 自动缓存 Template 对象。 |
减少 getTemplate() 调用 |
缓存 Template 实例,避免重复解析。 |
合理设置 template_update_delay |
生产环境设为 Long.MAX_VALUE ,避免 I/O 检查。 |
使用 StringTemplateLoader 动态模板 |
避免频繁 IO,适合配置化模板。 |
避免大模板 | 拆分模板,使用 include 或 import 模块化。 |
监控模板加载性能 | 记录 getTemplate() 耗时,排查瓶颈。 |
八、总结
项目 | 推荐做法 |
---|---|
模板来源 | classpath(ClassTemplateLoader ) |
配置方式 | 单例 Configuration |
编码 | UTF-8 |
热更新 | 开发开启,生产关闭 |
错误处理 | 使用 RETHROW_HANDLER |
性能 | 缓存 + 减少 IO |
✅ 一句话总结:
TemplateLoader
是 FreeMarker 的“模板寻址器”,选择合适的加载器并正确配置 Configuration
,是高效使用 FreeMarker 的第一步。