Freemarker基础

206 阅读4分钟

test

利用模板生成文件,常见使用就是用来生成一些重复代码,提高人的效率。

简介

FreeMarker 是一个用 Java 语言编写的模板引擎,它基于模板来生成文本输出
FreeMarker的诞生是为了取代JSP

优点:

  1. 没有其他的任何依赖,仅仅依赖Java自身
  2. 简单易用,功能强大
  3. 只能作为文本输出(HTML网页、电子邮件、配置文件、源代码等)。如果要生成word,excel等office,可以使用 poi

基础

语法学习

工作原理

yuanli.png

解释: 首先有一个模板文件,里边是一些文本和 freemarker 的语法,会使用 java 代码中表示的变量取替换掉,最终把替换后的文件输出。

模板文件的组成
  1. 文本:直接输出的部分
  2. 注释:使用 <#-- ... --> 格式做注释,里面内容不会输出
  3. 插值:即${...}或#{...}格式的部分,类似于占位符,将使用数据模型中的部分替代输出
  4. FTL指令:即 FreeMarker 指令,全称是:FreeMarker Template Language,和HTML标记类似,但名字前加 # 予以区分,不会输出
模板例子
<html>
<head>
<title>Welcome to FreeMarker 中文官网</title><br> 
</head> 
<body>
<#-- 注释部分 --> 
<#-- 下面使用插值 --> 
<h1>Welcome ${user} !</h1><br> 
<p>We have these animals:<br> 
<u1>
<#-- 使用FTL指令 animals 是一个数组,being是其中一个元素 --> 
<#list animals as being><br> 
  <li>${being.name} for ${being.price} Euros<br> 
<#list>
<u1>
</body> 
</html>
HTML转义

zhuanyi.png

FreeMarker 大于号>、小于号 <的使用 :与标签括号<>冲突的问题

解决方案:

  1. 用符号代替,例如:> gt,>= gte,< lt,<= lte
  2. 加括号 <#if(x>y)>
插值语法

定义:${...}
效果:插值的地方会被数据模型替换

assign指令

assign 用来为该模板页面创建或替换一个顶层变量,或者创建或替换多个变量等

简单语法:
<#assign name=value [in namespacehash]>
为变量name指定value值,in可选,将变量放入namespacehash命名空间中

定义简单变量:
<#assign name="Tom">

使用:
my name is ${name}

定义对象类变量:
<#assign info={"mobile":"xxxxxx","address":"china"} >

使用:
my mobile is ${info.mobile}, my address is ${info.address}

一个 assign 标记来进行多次定义:

<#assign
seq = ["foo", "bar", "baz"]
x++/>
判空方法

在插值中,如果为变量为 null,FreeMarker 就会报错。

  1. 添加默认值 ! 后为默认值
    ${mouse!"No mouse."}
    当mouse不存在时,返回No mouse.

    (product.color)!"red"product.color!"red" 区别?
    前者:可以处理product 和color为null的情况
    后者:只处理color为null的情况

  2. 测试变量是否为null,在变量后添加 ??

    <#if name??>
    …… name有值
    </#if>
    
  3. ?exists:旧版本的用法

list指令

简单介绍:用来遍历和循环的。

基本语法
<#list userList as user>
    用户名:${user.userName},密码:${user.userPassword},年龄: ${user.age}
</#list>

userList 是一个集合,包含了很多个user对象
遍历userList,把每一个变量的值读到循环变量user中。

  • list指令还包含了2个隐藏的循环变量
    • item_index:当前迭代项在所有迭代项中的位置,是数字值。
    • item_has_next 用于判断当前迭代项是否是所有迭代项中的最后一项

      其中 item是指循环变量名,如上例中 就是 user_index user_has_next

  • 在循环过程中,如果您想跳出循环,那么可以使用结合 break 指令,即<#break>来完成

进阶语法:TODO

嵌套循环

freemarker 允许嵌套循环。使用如下:

<#list userList as user>
    用户名:${user.userName},密码:${user.userPassword},年龄: ${user.age}<br/>
    <#list user.hobbies as hobby>
        爱好:${hobby}
    </#list>
</#list>

说明:userList 列表中包含多个user对象。而user中又包含hobbies列表。

java使用

  1. Maven工程添加依赖

    maven仓库

    <dependency>
    <groupId>org.FreeMarker</groupId>
    <artifactId>FreeMarker</artifactId>
    <version>2.3.29</version>
    </dependency>
    
  2. 具体使用步骤

    // 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是FreeMarker对于的版本号。
    Configuration configuration = new Configuration(Configuration.getVersion());
    
    // 第二步:设置模板文件所在的路径。
    configuration.setDirectoryForTemplateLoading(new File("/WEB-INF/ftl"));
    
    // 第三步:设置模板文件使用的字符集。一般就是utf-8.
    configuration.setDefaultEncoding("utf-8");
    
    // 第四步:加载一个模板,创建一个模板对象。
    Template template = configuration.getTemplate("hello.ftl");
    
    // 第五步:创建一个模板使用的数据集,可以是pojo也可以是map。一般是Map。
    Map dataModel = new HashMap<>();
    //向数据集中添加数据
    dataModel.put("hello", "this is my first FreeMarker test.");
    
    // 第六步:创建一个Writer对象,一般创建一FileWriter对象,指定生成的文件名。
    Writer out = new FileWriter(new File("/hello.html"));
    
    // 第七步:调用模板对象的process方法输出文件。
    template.process(dataModel, out);
    
    // 第八步:关闭流。
    out.close();
    

实战

代码生成

实现一个生成java实体类的例子。

编写工具类

FreeMarkUtil.java 如下:

import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * @author 
 * @date 1/30/24
 */
public class FreeMarkUtil {

    private static final Configuration cfg =  new Configuration(Configuration.getVersion());

    static {
        cfg.setDefaultEncoding(StandardCharsets.UTF_8.name());
    }

    /**
     * 提供模板名和数据
     * @param templateName
     * @param root 可以是pojo,也可是map
     * @return 字符串形式的文件内容
     * @throws Exception
     */
    public static String process(String templateName, Object root) throws Exception {
        // 类加载器模板加载器
        ClassTemplateLoader ctl = new ClassTemplateLoader(FreeMarkUtil.class.getClassLoader(), "template");
        // 多模板加载器
        TemplateLoader[] loaders = new TemplateLoader[] {  ctl };
        MultiTemplateLoader mul = new MultiTemplateLoader(loaders);
        // 设置模板加载器
        cfg.setTemplateLoader(mul);

        // 创建模版对象
        Template template = cfg.getTemplate(templateName+".ftl");

        String result;
        try(StringWriter writer = new StringWriter()) {
            // 执行模板
            template.process(root, writer);
            result = writer.toString();
        }
        
        return result;
    }

}


编写模板文件

模板文件 entity_template.ftl 如下:

package com.meizi.demo.dto.${intfacPackName};

<#assign DateFirst = true
TimestampFirst = true
ListFirst = true
/>
<#list fields as fItem>
    <#if fItem.classType == "Date" && DateFirst>
import java.util.Date;
        <#assign DateFirst = false />
    <#elseif fItem.classType == "Timestamp" && TimestampFirst>
import java.sql.Timestamp;
        <#assign TimestampFirst = false />
    <#elseif fItem.classType == "List" && ListFirst>
import java.util.List;
        <#assign ListFirst = false />
    <#else >
    </#if>
</#list>

/**
 * @author
 * create time: ${date}
 */ 
public class ${apiRequestClassName} {

    private static final long serialVersionUID = 1L;

    <#list fields as fItem>
    /**
     * ${fItem.desc}
     */ 
    private ${fItem.classType} ${fItem.name};

    </#list>

    <#list fields as fItem>
    public void set${fItem.name}(${fItem.classType} ${fItem.camelName}){
        this.${fItem.name} = ${fItem.camelName};
    }

    public ${fItem.classType} get${fItem.name}(){
        return ${fItem.name};
    }

    </#list>

}

模板文件放在资源目录下,如下图

ftl目录.png

编写客户端代码

如下:

    // 客户端类
    public static void main(String[] args) throws Exception {

        ServiceRequest serviceRequest = new ServiceRequest();
        serviceRequest.setApiRequestClassName("Requset");
        serviceRequest.setDate("2020-12-23");
        serviceRequest.setIntfacPackName("req");
        ArrayList<ServiceField> objects1 = new ArrayList<>();
        ServiceField serviceField = new ServiceField();
        serviceField.setCamelName("creditCard");
        serviceField.setClassType("String");
        serviceField.setDesc("信用卡");
        serviceField.setName("CreditCard");
        objects1.add(serviceField);
        serviceRequest.setFields(objects1);
        String trs_temlpate3 = FreeMarkUtil.process("entity_template", serviceRequest);
        System.out.println(trs_temlpate3);
    }

    // ServiceRequest 类
    public class ServiceRequest {
    /**
     * 生成文件的日期
     */
    private String date;
    /**
     * 包名
     */
    private String intfacPackName;
    /**
     * 类名
     */
    private String apiRequestClassName;
    /**
     * 字段名
     */
    private List<ServiceField> fields;
    //... 省略 getter setter方法
    }

    // ServiceField 类
    public class ServiceField {
    /**
     * 注释
     */
    private String desc;
    /**
     * 字段类型
     */
    private String classType;
    /**
     * 字段名 (帕斯卡风格)
     */
    private String name;
    /**
     * 字段名 (驼峰风格)
     */
    private String camelName;
    //... 省略 getter setter方法
    }

运行 main 方法,打印如下

result.png

参考文献

  1. www.freemarker.net/