版本兼容性说明:本文基于 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. TemplateLoaderConfiguration 的关系

  • 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 文件系统权限不足 检查运行用户对模板目录的读权限

四、注意事项

  1. 路径分隔符

    • 所有路径使用 /,即使在 Windows 上。
    • 不要以 / 开头(除非 loader 明确要求)。
  2. classpath 路径

    • ClassTemplateLoader 的路径是相对于 classpath 根的。
    • 如果模板在 src/main/resources/templates/,则 loader 路径应为 /templates
  3. 编码一致性

    • 模板文件保存为 UTF-8。
    • cfg.setDefaultEncoding("UTF-8") 必须设置。
  4. 线程安全

    • Configuration 是线程安全的,可全局单例。
    • Template 实例也是线程安全的,可缓存复用。
  5. 资源释放

    • 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())
            );
        }
    }
);

六、最佳实践

  1. 使用 ClassTemplateLoader 作为默认加载器(适合大多数应用)。
  2. 模板路径统一管理:定义常量或配置文件管理模板名。
  3. 避免频繁创建 Configuration:使用单例模式。
  4. 模板命名规范.ftl 后缀,语义化命名(如 user/profile.ftl)。
  5. 开发 vs 生产环境区分
    • 开发:开启模板自动刷新。
    • 生产:关闭刷新,提升性能。

七、性能优化

优化项 说明
启用模板缓存 FreeMarker 默认启用,Configuration 自动缓存 Template 对象。
减少 getTemplate() 调用 缓存 Template 实例,避免重复解析。
合理设置 template_update_delay 生产环境设为 Long.MAX_VALUE,避免 I/O 检查。
使用 StringTemplateLoader 动态模板 避免频繁 IO,适合配置化模板。
避免大模板 拆分模板,使用 includeimport 模块化。
监控模板加载性能 记录 getTemplate() 耗时,排查瓶颈。

八、总结

项目 推荐做法
模板来源 classpath(ClassTemplateLoader
配置方式 单例 Configuration
编码 UTF-8
热更新 开发开启,生产关闭
错误处理 使用 RETHROW_HANDLER
性能 缓存 + 减少 IO

一句话总结
TemplateLoader 是 FreeMarker 的“模板寻址器”,选择合适的加载器并正确配置 Configuration,是高效使用 FreeMarker 的第一步。