在 FreeMarker 模板引擎中,<#macro>
是用于定义可复用模板片段的指令,而 <@macroName>
则是用于调用这些命名模板的语法。合理使用 <#macro>
和 <@macroName>
可以极大提升模板的模块化程度、可维护性和代码复用性。
本文将从 核心概念、操作步骤、常见错误、注意事项、使用技巧、最佳实践 与 性能优化 等多个维度,全面讲解 FreeMarker 中 <#macro>
和 <@macroName>
的使用,帮助你快速掌握关键知识并实践。
一、核心概念
概念 | 描述 |
---|---|
<#macro> |
用于定义一个命名模板,可接受参数,返回 HTML 或文本内容 |
<@macroName> |
用于调用已定义的命名模板 |
参数传递 | 支持位置参数和命名参数 |
作用域 | <#macro> 定义在模板的任意位置,但调用需在其定义之后 |
嵌套使用 | 可在宏中调用其他宏,实现模块化开发 |
二、操作步骤(详细)
1. 定义一个最简单的宏
<#macro greet>
<p>Hello, world!</p>
</#macro>
2. 调用宏
<@greet />
输出结果:
<p>Hello, world!</p>
3. 定义带参数的宏(命名参数)
<#macro greet name>
<p>Hello, ${name}!</p>
</#macro>
调用方式:
<@greet name="Alice" />
4. 定义带多个参数的宏
<#macro userCard name age role>
<div class="user">
<p>Name: ${name}</p>
<p>Age: ${age}</p>
<p>Role: ${role}</p>
</div>
</#macro>
调用方式:
<@userCard name="Bob" age=25 role="Admin" />
5. 使用默认参数值(FreeMarker 2.3.29+)
<#macro sayHello name="Guest">
<p>Hello, ${name}!</p>
</#macro>
调用方式:
<@sayHello /> <#-- 输出 Hello, Guest! -->
<@sayHello name="Alice" /> <#-- 输出 Hello, Alice! -->
6. 使用 <#nested>
定义可嵌套内容的宏
<#macro box title>
<div class="box">
<h3>${title}</h3>
<div class="content">
<#nested>
</div>
</div>
</#macro>
调用方式:
<@box title="User Info">
<p>This is the user information.</p>
</@box>
7. 宏中定义局部变量 <#local>
<#macro greet name>
<#local greeting = "Hi">
<p>${greeting}, ${name}!</p>
</#macro>
调用方式:
<@greet name="Alice" />
8. 宏中引用其他宏
<#macro header>
<h1>My Website</h1>
</#macro>
<#macro page>
<@header />
<p>This is the main content.</p>
</#macro>
调用方式:
<@page />
三、常见错误与注意事项
1. 调用未定义的宏
<@unknownMacro /> <#-- 抛出异常:macro not found -->
✅ 解决方法:确保宏已定义,且调用位置在其定义之后。
2. 参数名拼写错误或缺失
<@greet namee="Alice" /> <#-- 报错:未定义参数 namee -->
✅ 解决方法:检查宏定义中的参数名,确保一致。
3. 忘记闭合 <#nested>
的内容
<@box title="Title"> <#-- 缺少闭合标签 -->
✅ 解决方法:使用 <@box title="Title">...</@box>
包裹内容。
4. <#local>
变量作用域错误
<#macro test>
<#local name = "Alice">
</#macro>
<p>${name}</p> <#-- 报错:name 不存在 -->
✅ 解决方法:<#local>
变量仅在宏内部可见。
5. 宏名重复定义
<#macro greet>...</#macro>
<#macro greet>...</#macro> <#-- 报错:重复定义 -->
✅ 解决方法:宏名应唯一,或使用命名空间模拟(如 my.greet
)。
四、使用技巧
1. 使用宏实现 UI 组件复用
<#macro button label href>
<a href="${href}" class="btn">${label}</a>
</#macro>
调用:
<@button label="Submit" href="/submit" />
2. 使用宏封装重复结构(如表格行、卡片等)
<#macro userRow user>
<tr>
<td>${user.name}</td>
<td>${user.age}</td>
<td>${user.role}</td>
</tr>
</#macro>
调用:
<table>
<#list users as user>
<@userRow user=user />
</#list>
</table>
3. 使用宏定义默认布局结构
<#macro layout title>
<html>
<head><title>${title}</title></head>
<body>
<#nested>
</body>
</html>
</#macro>
调用:
<@layout title="Home Page">
<h1>Welcome</h1>
<p>This is the home page.</p>
</@layout>
4. 使用宏封装逻辑判断
<#macro ifNotEmpty list>
<#if list?has_content>
<#nested>
</#if>
</#macro>
调用:
<@ifNotEmpty list=users>
<ul>
<#list users as user>
<li>${user.name}</li>
</#list>
</ul>
</@ifNotEmpty>
五、最佳实践
实践 | 说明 |
---|---|
✅ 使用 <#macro> 封装重复内容 |
如按钮、卡片、表格行等,提升可维护性 |
✅ 使用 <#nested> 实现布局宏 |
如页面布局、盒子结构、条件封装等 |
✅ 使用 <#local> 避免变量污染 |
保证宏内部变量不干扰外部作用域 |
✅ 使用默认参数值 | 提高宏调用的灵活性 |
✅ 统一命名规范 | 如 component.header 、layout.sidebar 等 |
✅ 避免宏嵌套过深 | 否则模板可读性差 |
✅ 宏中避免复杂逻辑 | 复杂逻辑应在 Java 层处理 |
六、性能优化
优化点 | 说明 |
---|---|
✅ 避免宏中频繁调用方法 | 方法调用会降低性能,建议预处理后传入 |
✅ 使用 <#assign> 缓存宏中计算结果 |
减少重复计算 |
✅ 启用模板缓存 | FreeMarker 支持模板缓存,避免重复解析 |
✅ 合理使用 <#nested> |
避免嵌套过多层级,影响渲染效率 |
七、总结
指令 | 说明 | 推荐使用场景 |
---|---|---|
<#macro> |
定义可复用模板片段 | UI 组件、布局结构、封装逻辑 |
<@macroName> |
调用宏 | 模块化模板、提高可读性 |
<#nested> |
宏中插入内容 | 布局模板、条件封装 |
<#local> |
宏内定义局部变量 | 避免变量污染 |
命名参数 | 支持参数传递 | 提高宏灵活性 |
默认参数 | 支持参数默认值 | 提高宏易用性 |
八、参考示例代码(Java + FTL)
Java 示例数据
Map<String, Object> model = new HashMap<>();
// 用户列表
List<Map<String, Object>> users = new ArrayList<>();
Map<String, Object> user1 = new HashMap<>();
user1.put("name", "Alice");
user1.put("age", 25);
user1.put("role", "Admin");
users.add(user1);
model.put("users", users);
FTL 模板示例
<h2>宏定义与调用示例</h2>
<#macro greet name="Guest">
<p>Hello, ${name}!</p>
</#macro>
<#macro userCard user>
<div class="user">
<p>Name: ${user.name}</p>
<p>Age: ${user.age}</p>
<p>Role: ${user.role}</p>
</div>
</#macro>
<#macro layout title>
<html>
<head><title>${title}</title></head>
<body>
<#nested>
</body>
</html>
</#macro>
<#-- 调用宏 -->
<@greet name="Bob" />
<#-- 调用用户卡片 -->
<#list users as user>
<@userCard user=user />
</#list>
<#-- 布局宏调用 -->
<@layout title="User Page">
<h2>User Info</h2>
<#list users as user>
<@userCard user=user />
</#list>
</@layout>
九、结语
掌握 FreeMarker 中 <#macro>
与 <@macroName>
的使用,是构建高效、模块化模板系统的关键。通过宏机制,可以实现 UI 组件复用、布局封装、逻辑抽象等功能,极大提升模板的可读性和可维护性。
✅ 最终建议:
- 所有重复内容应封装为宏;
- 使用
<#nested>
实现布局结构; - 使用
<#local>
避免变量污染; - 使用默认参数提高宏灵活性;
- 避免宏嵌套过深;
- 启用模板缓存提升性能;
- 宏名统一命名规范,避免冲突。