适用版本:FreeMarker 2.3.23+(支持模板继承)
适用场景:Web 开发中实现模板复用、页面布局统一(如头部、尾部、侧边栏等)


一、核心概念

FreeMarker 支持模板继承(Template Inheritance),通过 <#extends><#block> 实现类似“父模板”与“子模板”的关系。

指令 作用
<#extends "parent.ftl"> 指定当前模板继承自某个父模板(布局模板)
<#block name="...">...</#block> 定义可被子模板覆盖的“占位块”
<#block name="...">默认内容</#block> 块中可包含默认内容,子模板可选择性覆盖
<#override name="...">...</#override> 子模板中覆盖父模板的 block 内容
<#include "header.ftl"> 包含(include)静态片段,不参与继承

📌 类比理解

  • <#extends> 类似 Java 的 extends 关键字
  • <#block> 类似“抽象方法”或“钩子”,允许子类重写

二、操作步骤(超详细,适合快速上手)

步骤 1:创建父模板(布局模板)layout.ftl

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title><#block name="title">默认标题</#block></title>
    <link rel="stylesheet" href="/css/main.css">
    <#block name="head"> <!-- 可选的额外 head 内容 -->
    </#block>
</head>
<body>
    <header>
        <h1>我的网站</h1>
        <nav>首页 | 关于 | 联系</nav>
    </header>

    <main>
        <#block name="content">
            <p>这里是默认内容。</p>
        </#block>
    </main>

    <footer>
        <#block name="footer">
            &copy; 2025 版权所有
        </#block>
    </footer>

    <#block name="script">
        <script src="/js/common.js"></script>
    </#block>
</body>
</html>

✅ 说明:定义了多个 <#block>,如 titlecontentfooter 等,子模板可覆盖。


步骤 2:创建子模板 home.ftl,继承父模板

<#extends "layout.ftl">

<#block name="title">首页 - 我的网站</#block>

<#block name="content">
    <h2>欢迎来到首页</h2>
    <p>这是首页的专属内容。</p>
    <ul>
        <#list ["新闻", "产品", "服务"] as item>
            <li>${item}</li>
        </#list>
    </ul>
</#block>

<#block name="footer">
    ${super} <!-- 保留父模板的 footer 内容 -->
    <br>联系电话:123-456-7890
</#block>

<#block name="script">
    ${super}
    <script src="/js/home.js"></script>
</#block>

🔑 关键点:

  • <#extends "layout.ftl"> 必须放在文件最顶部
  • 使用 <#block name="..."> 覆盖父模板中的同名 block
  • ${super} 表示插入父模板中该 block 的原始内容

步骤 3:创建另一个子模板 about.ftl

<#extends "layout.ftl">

<#block name="title">关于我们</#block>

<#block name="content">
    <h2>关于我们</h2>
    <p>我们是一家专注于技术创新的公司。</p>
</#block>

<#block name="footer">
    关于页尾部信息
</#block>

✅ 注意:about.ftl 没有覆盖 script,所以会使用父模板中的默认脚本。


步骤 4:在 Java 后端渲染模板(Spring Boot 示例)

@Configuration
public class FreeMarkerConfig {
    @Bean
    public freemarker.template.Configuration freemarkerConfiguration() {
        freemarker.template.Configuration config = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_31);
        config.setClassForTemplateLoading(this.getClass(), "/templates");
        config.setDefaultEncoding("UTF-8");
        return config;
    }
}
@Controller
public class PageController {
    @Autowired
    private freemarker.template.Configuration freemarkerConfig;

    @GetMapping("/home")
    public String home(Model model) throws IOException, TemplateException {
        Template template = freemarkerConfig.getTemplate("home.ftl");
        StringWriter out = new StringWriter();
        template.process(model.asMap(), out);
        model.addAttribute("content", out.toString());
        return "base"; // 返回主页面容器(可选)
    }
}

💡 实际中通常配合 Spring MVC 的 ViewResolver 自动处理,无需手动 process。


三、常见错误与解决方案

错误现象 原因 解决方案
TemplateException: Cannot find template "layout.ftl" 路径错误或模板未找到 检查模板路径、类路径、拼写
<#extends> must be the first thing in the template <#extends> 不在第一行 确保 <#extends> 是文件第一行,前面不能有空格或换行
block 内容未被覆盖 名称拼写错误或未使用 <#block> 检查 block 名称是否一致,大小写敏感
${super} 无效 父 block 为空或语法错误 确保父模板中该 block 有内容,且 ${super}<#block> 内部
多层继承混乱 继承层级过深或逻辑不清 避免超过 2 层继承,使用 include 拆分公共片段

四、注意事项

  1. <#extends> 必须是模板第一行,前面不能有任何空格、BOM 或换行。
  2. block 名称区分大小写contentContent 被视为不同。
  3. ${super} 只能在 <#block> 内使用,用于插入父模板内容。
  4. 不支持多重继承:一个模板只能 <#extends> 一个父模板。
  5. 避免循环继承:A extends B,B 不能 extends A。
  6. 模板路径是相对路径:相对于 Configuration 设置的模板加载目录。

五、使用技巧

1. 使用 `$

<#block name="script">
    ${super}
    <script src="/js/custom.js"></script>
</#block>

2. 定义可选 block(带默认内容)

<#block name="sidebar">
    <div class="sidebar">默认侧边栏</div>
</#block>

子模板可选择覆盖或忽略。

3. 多级继承(谨慎使用)

<!-- base.ftl -->
<#block name="header">通用头部</#block>

<!-- section.ftl -->
<#extends "base.ftl">
<#block name="header">栏目头部</#block>

<!-- page.ftl -->
<#extends "section.ftl">
<#block name="header">页面头部</#block>

⚠️ 建议不超过 2 层,避免维护困难。

4. 使用 <#import><#include> 配合

  • <#include>:包含静态片段(如 header、footer)
  • <#import>:导入宏库,避免污染命名空间
<#include "fragments/header.ftl">
<#import "macros/util.ftl" as util>

六、最佳实践

实践 说明
✅ 统一布局模板 创建 layout.ftl 作为所有页面的基类
✅ 命名规范 block 名称使用小写+下划线:page_title, main_content
✅ 默认内容 为关键 block 提供默认内容,提高可读性
✅ 模块化 将 header、footer 等拆分为 <#include> 片段
✅ 避免过度继承 优先使用 <#include><#macro> 复用代码
✅ 使用版本控制 模板变更需团队同步,避免冲突

七、性能优化建议

  1. 启用模板缓存(默认开启):

    configuration.setTemplateCache(new freemarker.cache.MruCacheStorage(20, 250));
    
  2. 避免频繁 recompile:确保模板文件不频繁修改,FreeMarker 会自动缓存编译结果。

  3. 减少继承层级:每多一层继承,解析开销略增,建议扁平化设计。

  4. 使用 <#compress> 减少输出体积

    <#compress>
        <#include "header.ftl">
        <#block name="content">...</#block>
    </#compress>
    
  5. 异步渲染(高级):在高并发场景下,考虑异步生成静态页。


八、总结:快速掌握要点

要点 一句话总结
<#extends> 放在子模板第一行,指定父模板
<#block> 定义可被覆盖的区域
<#override> 旧语法,推荐用 <#block> 替代
${super} 插入父模板内容,实现“扩展”而非“完全替换”
路径问题 确保模板路径正确,使用相对路径
编码问题 统一使用 UTF-8 编码

一句话掌握精髓

<#extends> 定继承,<#block> 定区域,${super} 续父文,布局复用轻松成。


通过以上详细指南,你已具备在项目中熟练使用 FreeMarker 模板继承的能力。建议动手实践 layout.ftl + home.ftl 示例,快速验证效果。