适用版本: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">
© 2025 版权所有
</#block>
</footer>
<#block name="script">
<script src="/js/common.js"></script>
</#block>
</body>
</html>
✅ 说明:定义了多个
<#block>
,如title
、content
、footer
等,子模板可覆盖。
步骤 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 拆分公共片段 |
四、注意事项
<#extends>
必须是模板第一行,前面不能有任何空格、BOM 或换行。- block 名称区分大小写:
content
与Content
被视为不同。 ${super}
只能在<#block>
内使用,用于插入父模板内容。- 不支持多重继承:一个模板只能
<#extends>
一个父模板。 - 避免循环继承:A extends B,B 不能 extends A。
- 模板路径是相对路径:相对于
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> 复用代码 |
✅ 使用版本控制 | 模板变更需团队同步,避免冲突 |
七、性能优化建议
启用模板缓存(默认开启):
configuration.setTemplateCache(new freemarker.cache.MruCacheStorage(20, 250));
避免频繁 recompile:确保模板文件不频繁修改,FreeMarker 会自动缓存编译结果。
减少继承层级:每多一层继承,解析开销略增,建议扁平化设计。
使用
<#compress>
减少输出体积:<#compress> <#include "header.ftl"> <#block name="content">...</#block> </#compress>
异步渲染(高级):在高并发场景下,考虑异步生成静态页。
八、总结:快速掌握要点
要点 | 一句话总结 |
---|---|
<#extends> |
放在子模板第一行,指定父模板 |
<#block> |
定义可被覆盖的区域 |
<#override> |
旧语法,推荐用 <#block> 替代 |
${super} |
插入父模板内容,实现“扩展”而非“完全替换” |
路径问题 | 确保模板路径正确,使用相对路径 |
编码问题 | 统一使用 UTF-8 编码 |
✅ 一句话掌握精髓:
<#extends>
定继承,<#block>
定区域,${super}
续父文,布局复用轻松成。
通过以上详细指南,你已具备在项目中熟练使用 FreeMarker 模板继承的能力。建议动手实践 layout.ftl
+ home.ftl
示例,快速验证效果。