test
利用模板生成文件,常见使用就是用来生成一些重复代码,提高人的效率。
简介
FreeMarker 是一个用 Java 语言编写的模板引擎,它基于模板来生成文本输出。
FreeMarker的诞生是为了取代JSP
优点:
- 没有其他的任何依赖,仅仅依赖Java自身
- 简单易用,功能强大
- 只能作为文本输出(HTML网页、电子邮件、配置文件、源代码等)。如果要生成word,excel等office,可以使用
poi
基础
语法学习
工作原理
解释: 首先有一个模板文件,里边是一些文本和 freemarker 的语法,会使用 java 代码中表示的变量取替换掉,最终把替换后的文件输出。
模板文件的组成
- 文本:直接输出的部分
- 注释:使用
<#-- ... -->格式做注释,里面内容不会输出 - 插值:即
${...}或#{...}格式的部分,类似于占位符,将使用数据模型中的部分替代输出 - 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转义
FreeMarker 大于号>、小于号 <的使用 :与标签括号<>冲突的问题
解决方案:
- 用符号代替,例如:> gt,>= gte,< lt,<= lte
- 加括号 <#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 就会报错。
-
添加默认值
!后为默认值
${mouse!"No mouse."}
当mouse不存在时,返回No mouse.(product.color)!"red"和product.color!"red"区别?
前者:可以处理product 和color为null的情况
后者:只处理color为null的情况 -
测试变量是否为null,在变量后添加
??<#if name??> …… name有值 </#if> -
?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使用
-
Maven工程添加依赖
maven仓库
<dependency> <groupId>org.FreeMarker</groupId> <artifactId>FreeMarker</artifactId> <version>2.3.29</version> </dependency> -
具体使用步骤
// 第一步:创建一个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>
}
模板文件放在资源目录下,如下图
编写客户端代码
如下:
// 客户端类
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 方法,打印如下