在 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.headerlayout.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> 避免变量污染;
  • 使用默认参数提高宏灵活性;
  • 避免宏嵌套过深;
  • 启用模板缓存提升性能;
  • 宏名统一命名规范,避免冲突。