杰瑞科技汇

Freemarker如何生成Java代码?

目录

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

核心原理

FreeMarker 的工作流程非常清晰,主要包含三个角色:

Freemarker如何生成Java代码?-图1
(图片来源网络,侵删)
  • 数据模型:一个 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 引擎来处理模板。

Freemarker如何生成Java代码?-图2
(图片来源网络,侵删)

编写 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.javamain 方法。

生成的结果 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 也能很好地处理。

Freemarker如何生成Java代码?-图3
(图片来源网络,侵删)

假设数据模型是:

// 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 代码,极大地提升开发效率和规范性。

分享:
扫描分享到社交APP
上一篇
下一篇