目录
- 核心原理:FreeMarker 是如何工作的?
- 环境搭建
- 添加 Maven 依赖
- 创建项目结构
- 准备 Java 模板
- 创建
.ftl模板文件 - FreeMarker 语法详解(在模板中如何使用数据)
- 创建
- 编写 Java 代码
- 准备数据模型
- 加载模板并生成文件
- 完整示例
- 模板文件 (
User.ftl) - Java 代码 (
CodeGenerator.java) - 生成的结果 (
User.java)
- 模板文件 (
- 进阶技巧
- 处理基本数据类型和包装类型
- 处理集合/列表
- 处理日期格式化
- 使用
#macro定义可复用的代码块
核心原理
FreeMarker 的工作流程非常清晰,主要包含三个角色:

- 数据模型:一个 Java 对象,通常是一个
Map或一个 POJO,它包含了模板中需要用到的所有数据,类名、字段名、字段类型等。 - 模板:一个
.ftl文件,它混合了静态文本和 FreeMarker 的动态指令,指令会从数据模型中获取数据并插入到最终输出中。 - FreeMarker 引擎:负责读取模板文件,结合数据模型,将两者合并,最终生成一个文本输出(比如一个
.java文件)。
步骤一:环境搭建
添加 Maven 依赖
在你的 pom.xml 文件中添加 FreeMarker 的核心依赖。
<dependencies>
<!-- FreeMarker 核心库 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version> <!-- 建议使用较新版本 -->
</dependency>
</dependencies>
创建项目结构
为了清晰,我们创建一个简单的项目结构:
your-project/
├── pom.xml
└── src/
└── main/
├── java/
│ └── com/
│ └── example/
│ └── generator/
│ └── CodeGenerator.java <-- 我们的生成器主类
└── resources/
└── templates/
└── User.ftl <-- 我们的 Java 模板文件
步骤二:准备 Java 模板
模板文件是 FreeMarker 的灵魂,它看起来很像一个普通的 Java 文件,但其中包含了 FreeMarker 的特殊语法。
创建模板文件 src/main/resources/templates/User.ftl
package ${package_name};
import java.io.Serializable;
<#list imports as import>
import ${import};
</#list>
/**
* <#if tableComment??>${tableComment}</#if>
*/
public class ${className} implements Serializable {
private static final long serialVersionUID = 1L;
<#list fields as field>
<#if field.comment??>
/**
* ${field.comment}
*/
</#if>
private ${field.type} ${field.name};
</#list>
<#list fields as field>
public ${field.type} get${field.name?cap_first}() {
return this.${field.name};
}
public void set${field.name?cap_first}(${field.type} ${field.name}) {
this.${field.name} = ${field.name};
}
</#list>
@Override
public String toString() {
return "${className}{" +
<#list fields as field>
"${field.name}=" + ${field.name} +
<#if field_has_next>
", " +
</#if>
</#list>
'}';
}
}
FreeMarker 语法详解
- 插值,用于在输出中显示数据模型中的值。
${package_name}:会从数据模型中查找package_name这个 key 的值。${field.name?cap_first}: 后面是内置函数。cap_first表示将首字母大写,用于生成 getter/setter 方法。
<#...>:指令,用于控制逻辑。<#list fields as field>:循环遍历fields列表,每次循环将元素赋值给field变量。<#if ...>:条件判断。<#if field_has_next>:在list循环内部使用,判断是否还有下一个元素。<#list imports as import>:循环遍历需要导入的包列表。
- 注释,FreeMarker 会忽略这部分内容。
步骤三:编写 Java 代码
这是执行生成逻辑的核心类,我们将在这里准备数据模型,并调用 FreeMarker 引擎来处理模板。

编写 CodeGenerator.java
package com.example.generator;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CodeGenerator {
public static void main(String[] args) {
// 1. 创建一个 FreeMarker 配置实例
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
try {
// 2. 设置模板加载器:告诉 FreeMarker 去哪里找模板文件
// 这里设置为从 classpath 的 "templates" 目录加载
cfg.setDirectoryForTemplateLoading(new File("src/main/resources/templates"));
// 3. 准备数据模型
Map<String, Object> dataModel = createDataModel();
// 4. 加载模板文件
Template template = cfg.getTemplate("User.ftl");
// 5. 定义输出文件的路径和名称
File outputFile = new File("src/main/java/com/example/entity/User.java");
// 确保父目录存在
outputFile.getParentFile().mkdirs();
// 6. 创建一个 Writer 对象来写入生成的文件
try (Writer out = new FileWriter(outputFile)) {
// 7. 处理模板并生成文件
// 第一个参数是数据模型,第二个参数是输出流
template.process(dataModel, out);
System.out.println("Java 文件生成成功!路径: " + outputFile.getAbsolutePath());
}
} catch (IOException | TemplateException e) {
e.printStackTrace();
}
}
/**
* 创建并返回数据模型
*/
private static Map<String, Object> createDataModel() {
Map<String, Object> dataModel = new HashMap<>();
// --- 基本信息 ---
dataModel.put("package_name", "com.example.entity");
dataModel.put("className", "User");
dataModel.put("tableComment", "用户信息表");
// --- 需要导入的包 ---
List<String> imports = new ArrayList<>();
imports.add("java.util.Date");
imports.add("javax.persistence.Column");
dataModel.put("imports", imports);
// --- 类的字段列表 ---
List<Map<String, String>> fields = new ArrayList<>();
// 字段1: id
Map<String, String> field1 = new HashMap<>();
field1.put("name", "id");
field1.put("type", "Long");
field1.put("comment", "用户ID");
fields.add(field1);
// 字段2: username
Map<String, String> field2 = new HashMap<>();
field2.put("name", "username");
field2.put("type", "String");
field2.put("comment", "用户名");
fields.add(field2);
// 字段3: age
Map<String, String> field3 = new HashMap<>();
field3.put("name", "age");
field3.put("type", "Integer");
field3.put("comment", "年龄");
fields.add(field3);
// 字段4: createTime
Map<String, String> field4 = new HashMap<>();
field4.put("name", "createTime");
field4.put("type", "Date");
field4.put("comment", "创建时间");
fields.add(field4);
dataModel.put("fields", fields);
return dataModel;
}
}
完整示例与结果
运行 CodeGenerator.java 的 main 方法。
生成的结果 src/main/java/com/example/entity/User.java
package com.example.entity;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
/**
* 用户信息表
*/
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 年龄
*/
private Integer age;
/**
* 创建时间
*/
private Date createTime;
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return this.age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getCreateTime() {
return this.createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", " +
"username=" + username +
", " +
"age=" + age +
", " +
"createTime=" + createTime +
'}';
}
}
可以看到,生成的 Java 文件完美地结合了模板中的静态文本和从 dataModel 中动态插入的数据。
进阶技巧
处理基本数据类型和包装类型
在模板中,我们通常使用包装类型(如 Integer, Long)以避免 null 指针问题,但如果数据源(如数据库)能保证不为空,也可以生成基本类型(如 int, long),可以在数据模型中增加一个字段来区分。
<#list fields as field>
<#if field.type == "int">
private ${field.type} ${field.name};
<#else>
private ${field.type} ${field.name};
</#if>
</#list>
处理集合/列表
如果数据模型中包含一个复杂的对象,而不是简单的 Map,FreeMarker 也能很好地处理。

假设数据模型是:
// dataModel
dataModel.put("user", new User(1L, "Alice", 30));
模板可以这样写:
Hello, ${user.username}! Your age is ${user.age}.
处理日期格式化
FreeMarker 有强大的日期格式化功能。
<#assign formattedDate = createTime?string("yyyy-MM-dd HH:mm:ss")>
The create time is: ${formattedDate}
?string("pattern") 是日期格式化的内置函数。
使用 #macro 定义可复用的代码块
#macro 类似于方法,可以定义一段可复用的模板代码。
在 User.ftl 中定义一个宏:
<#macro generateField field>
<#if field.comment??>
/**
* ${field.comment}
*/
</#if>
private ${field.type} ${field.name};
</#macro>
然后在其他地方调用它:
<#list fields as field>
<@generateField field=field />
</#list>
这样可以使模板更加整洁和模块化。
通过以上步骤和技巧,你就可以熟练地使用 FreeMarker 来生成各种 Java 代码,极大地提升开发效率和规范性。
