Freemarker生成案例

wen java案例 1

Freemarker模板引擎实战:从零到一构建动态代码生成器(附完整案例)

📚 目录导读

  1. 为什么需要Freemarker?——模板引擎的定位与价值
  2. 环境搭建与核心语法速览(含对比表格)
  3. 实战案例:自动生成Java实体类与MyBatis映射文件
  4. 进阶技巧:数据模型设计、空值处理与自定义指令
  5. 常见问题FAQ:开发中踩过的坑与解决方案
  6. SEO优化建议:让生成代码可维护、可搜索

为什么需要Freemarker?——模板引擎的定位与价值

在现代后端开发中,重复性代码(如DAO层、DTO、配置文件)往往占据项目30%以上的工作量,Freemarker作为一种基于Java的模板引擎,通过数据模型+模板文件分离的机制,将静态文本与动态数据完美融合,能极大提升开发效率。

Freemarker生成案例

核心价值场景

  • 代码生成器(如MyBatis Generator的定制)
  • 页面静态化(配合Spring MVC或独立使用)
  • 邮件/报表模板化(动态内容渲染)
  • 多环境配置切换(如不同环境的application.yml)

与JSP、Thymeleaf相比,Freemarker的强类型数据模型(支持Java对象直接映射)和宏(macro)复用机制,使其在代码生成领域具有不可替代的优势。


环境搭建与核心语法速览

1 Maven依赖(规避版本冲突)

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.32</version>
</dependency>

2 核心API三步骤

// 1. 创建配置实例(全局单例)
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
cfg.setDirectoryForTemplateLoading(new File("/path/to/templates"));
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
// 2. 合成数据模型(Map或POJO)
Map<String, Object> data = new HashMap<>();
data.put("className", "UserInfo");
data.put("fields", Arrays.asList("id", "name", "email"));
// 3. 渲染输出
Template template = cfg.getTemplate("entity.ftl");
try (Writer out = new FileWriter("UserInfo.java")) {
    template.process(data, out);
}

3 语法速查表(高频指令)

指令 作用 示例
${variable} 输出值(自动转义) ${user.name}
<#if> 条件判断 <#if isAdmin>...<#else>...
<#list> 遍历集合 <#list fields as f>${f}</#list>
<#assign> 定义变量 <#assign total = 10>
<#macro> 定义可复用片段 <@repeat count=3>Hello</@repeat>
?string 格式化数字/日期 ${price?string("0.00")}

实战案例:自动生成Java实体类与MyBatis映射文件

1 场景描述

已知数据库表 user_info,包含字段:id (Long), user_name (String), email (String),我们需要一键生成:

  • UserInfo.java(Lombok优化)
  • UserInfoMapper.java(接口)
  • UserInfoMapper.xml(SQL映射)

2 设计数据模型(Java侧)

public class TableMeta {
    private String tableName;      // 表名
    private String className;      // 类名
    private List<FieldMeta> fields;
    public static class FieldMeta {
        private String colName;    // 列名
        private String fieldName;  // 驼峰属性名
        private String javaType;   // Java类型
        private boolean primaryKey;
        // getter/setter...
    }
}

3 核心模板文件(entity.ftl)

package com.example.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ${className} {
<#list fields as field>
    <#if field.primaryKey>
    /** 主键 */
    private ${field.javaType} ${field.fieldName};
    <#else>
    /** ${field.colName} */
    private ${field.javaType} ${field.fieldName};
    </#if>
</#list>
}

4 模板引擎执行代码(含容错处理)

public class CodeGenerator {
    public static void main(String[] args) throws Exception {
        // 构建数据模型
        TableMeta meta = new TableMeta();
        meta.setClassName("UserInfo");
        meta.setTableName("user_info");
        meta.setFields(Arrays.asList(
            new FieldMeta("id", "id", "Long", true),
            new FieldMeta("user_name", "userName", "String", false),
            new FieldMeta("email", "email", "String", false)
        ));
        // 渲染并输出
        Map<String, Object> data = new HashMap<>();
        data.put("className", meta.getClassName());
        data.put("fields", meta.getFields());
        Configuration cfg = FreemarkerConfig.getInstance();
        Template template = cfg.getTemplate("entity.ftl");
        // 确保输出目录存在
        File outputDir = new File("generated");
        if (!outputDir.exists()) outputDir.mkdirs();
        try (Writer out = new FileWriter(new File(outputDir, meta.getClassName() + ".java"))) {
            template.process(data, out);
        }
        System.out.println("✅ 生成成功:" + meta.getClassName() + ".java");
    }
}

5 生成结果示例

package com.example.entity;
import lombok.Data;
@Data
public class UserInfo {
    /** 主键 */
    private Long id;
    /** user_name */
    private String userName;
    /** email */
    private String email;
}

进阶技巧:数据模型设计、空值处理与自定义指令

1 优雅的空值防护(避免输出"null")

${field.remark!''}  <!-- 默认空字符串 -->
${field.javaType!'Object'} <!-- 缺省类型 -->

2 自定义指令(宏)实现通用转换

<#macro toCamelCase str>
    <#assign parts = str?split("_")>
    <#assign result = "">
    <#list parts as part>
        <#if part?length gt 0>
            <#assign result = result + part?cap_first>
        </#if>
    </#list>
    ${result?uncap_first}
</#macro>
使用:<@toCamelCase str="user_name"/>  <!-- 输出 userName -->

3 性能优化建议

  • 模板缓存cfg.setCacheStorage(new StrongCacheStorage()) 用于生产环境
  • 静态化输出:利用Freemarker生成静态HTML,减少DB压力
  • 懒加载:针对大数据列表,采用<#list> + ?chunk分块处理

常见问题FAQ:开发中踩过的坑与解决方案

Q1:模板中访问Java对象属性报错"Invalid reference"?

  • 原因:Java对象未提供getter方法,或字段为私有属性。
  • 解决:确保POJO使用标准getXxx(),或直接使用Map,推荐开启cfg.setClassicCompatible(true)兼容旧版语法。

Q2:中文乱码问题困扰已久?

  • 绝对路径:确保模板文件保存编码为UTF-8。
  • 输出流:统一使用OutputStreamWriter包裹的Writer,并指定UTF-8。错误示范new PrintWriter(new FileOutputStream(file), true)(默认平台编码)。

Q3:如何在循环中获取index下标?

<#list fields as field>
    ${field_index}  <!-- 内置变量:从0开始 -->
</#list>

Q4:模板中调用静态方法(如StringUtils)?

<#assign newStr = yourStaticUtilClass?new("fromClass")>
或直接:cfg.setSharedVariable("StringUtil", StringUtil.class);

SEO优化建议:让生成代码可维护、可搜索

尽管搜索引擎不索引代码文件,但在技术博客分享公司内部知识库中,模板的可读性直接影响团队协作效率:

  1. 模板文件内添加注释<#-- 生成时间: ${.now} -->,保留元信息。
  2. 输出格式规范化:使用<#rt>去除多余空白行,保证生成代码与手写代码风格一致。
  3. 统一变量命名:在数据模型层采用TableMetaFieldMeta等语义化名称,降低认知负担。

写作结语:Freemarker远不止于简单的字符串替换,它通过模板与逻辑分离的哲学,重构了我们处理重复性工作的方式,从本文的实体类生成案例出发,你可以轻松扩展至Controller层、Service层乃至前端Vue组件,掌握数据模型设计技巧,你就是团队的"代码复印机",欢迎评论区交流你的Freemarker奇技淫巧。

抱歉,评论功能暂时关闭!