系列文章:
Mavan:自定义骨架及工程初始化
低代码探索:Java 模板引擎技术
一 概述
在低代码探索:Java 模板引擎技术 中,我们介绍了freemarker的概念和简单使用示例。本篇会详细介绍一下freemarker中的表达式,这在使用时很重要。我们通过模板定义要生成的页面框架,通过表达式来实现参数占位/替换,输入变量的首字母大/小写转换,以及for循环遍历等等。通过模板与表达式的配合,生成所需的页面/代码文件。
二 模板
关于模板的介绍,可以先看看freemarker在线手册的内容。FTL (即FreeMarker template language),就是freemarker为编写模板设计的非常简单的编程语言。
2.1 FTL介绍
模板(FTL编程)是由如下部分混合而成的:
- 文本:文本会照着原样来输出。
- 插值:这部分的输出会被计算的值来替换,相当于模板中的“变量”。插值由
${
and}
所分隔(或者#{
and}
,这种风格已经不建议再使用了,使用 number_format 设置项 和 the string 内建函数 来代替。对于计算机使用 (也就非本地的格式化)的格式化,使用 c 内建函数 (比如number?c
)。)。例如,我们有两个变量x和y,x
=2.582
而y
=4
,那么常用的插值表达式和对应的取值如下面所示:
#{x} <#-- 2.582 -->
#{y} <#-- 4 -->
#{x; M2} <#-- 2.58 -->
#{y; M2} <#-- 4 -->
#{x; m1} <#-- 2.6 -->
#{y; m1} <#-- 4.0 -->
#{x; m1M2} <#-- 2.58 -->
#{y; m1M2} <#-- 4.0 -->
- FTL 标签:FTL标签和HTML标签很相似,但是它们却是给FreeMarker的指示, 而且不会打印在输出内容中。
- 注释:注释和HTML的注释也很相似,但它们是由
<#--
和-->
来分隔的。注释会被FreeMarker直接忽略, 更不会在输出内容中显示。
2.2 模板示例
下面是手册中提供的一个HTML页面的模板示例:其中,蓝色代表文本,橘黄色代表插值(${user}),黄色代表FTL标签(<#list animals ...),而绿色是注释, [BR]是为了在页面上显式可见的换行:
FTL有几点注意事项需要特别指出:
1、FTL区分大小写,例如 list 是指令的名称,而List不是(切记!),{name} 和 {Name} 或
2、插值 仅仅可以在 文本(或字符串表达式)中使用。
3、FTL 标签 不可以在其他 FTL 标签 和 插值中使用。比如, 这样做是 错误 的: <#if <#include 'foo'>='bar'>...</#if>
4、注释 可以放在 FTL 标签 和 插值中。示例:
代码语言:javascript复制<h1>Welcome ${user <#-- The name of user -->}!</h1>[BR]
<p>We have these animals:[BR]
<ul>[BR]
<#list <#-- some comment... --> animals as <#-- again... --> animal>[BR]
...
2.3 指令
FTL的指令是通过标签来调用,例如上面用到的<#list animals as animal>,使用到了list这个指令,代表遍历list中的每个变量。除了list之外,常用的还有<#if>
FTL的指令有两种类型: 预定义指令 和 用户自定义指令。详细说明可以参见链接,这里不再赘述,有疑问可以共同探讨。
2.4 表达式
FTL的表达式主要包括 直接确定值(字符串、数字、布尔值、序列、值域、哈希表)、检索变量、字符串操作、序列操作、哈希表操作等等。在定义模板时,使用最多的是直接确定值(字符串、数字),其次是检索变量和字符串操作。
检索变量示例:我们在外层定义好一个user对象,包含name, age等属性,那么在模板中应用时,可以通过 ${user.name}, ${user.age}来获取对应的值并替换到模板中对应的位置;
字符串操作示例:字符串连接:"Hello ${user}!" ;获取一个字符:name[0];字符串切分: 包含结尾: name[0..4]
,不包含结尾: name[0..<5]
,基于长度(宽容处理): name[0..*5]
,去除开头: name[5..]
。
2.5 插值
插值是用来给 表达式
插入具体值然后转换为文本(字符串)。用我们更熟悉的表述,就是模板中的占位符,用来标记某个位置是一个变量,在生成代码时,可以通过传入我们定义好的值,模板进行识别并完成替换,从而生成我们最终想要的文件。
插值的使用格式: {expression},这里的 expression 可以是所有种类的表达式(比如 {100 x})。
完整的示例和说明还是推荐查看手册:插值。
三 模板使用和生成示例
接下来,我们还是通过demo来阐述freemarker模板定义到生成文件的整个过程,jar包的引入方式在低代码探索:Java 模板引擎技术中已经有过说明,这里只列举模板和代码部分。
3.1 模板定义
test.java.ftl:
代码语言:javascript复制package ${packageName};
public class ${className} {
<#if colList??>
<#list colList as col>
private ${col.type} ${col.name};
</#list>
<#list colList as col>
public ${col.type} get${col.name?cap_first}(){
return ${col.name};
}
public void set${col.name?cap_first}(${col.type} ${col.name}){
this.${col.name}=${col.name};
}
</#list>
</#if>
}
这个模板定义了一个典型的实体类,也就是POJO。属性列表我们通过colList参数传入,并在外层加了if 的判断,避免参数为空的情况;对于list中的每个变量,都是一个col对象,里面有type 和 name两个属性,上述模板先逐个生成 private Integer id; xxx 这些字段,然后再依次生成对应的getId setId。。。 方法。
因为get set方法需要的是变量名的首字母大写,所以这里还使用了 get${col.name?cap_first}来对首字母进行大写处理。
3.2 column实体类定义
代码语言:javascript复制package com.freemark.demo.entity;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class Column {
private String type;
private String name;
}
Column用于封装参数,有type和name两个属性。 这里为了简化代码 引入了lombok,在pom.xml中增加相关依赖引入即可:
代码语言:javascript复制<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
3.3 生成代码方法
主要就是完成参数封装,模板加载和生成方法与上一篇内容相似。
代码语言:javascript复制package com.freemark.demo.templates.util;
import com.freemark.demo.entity.Column;
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FreemarkerTest {
private static final String TEMPLATE_PATH = "src/main/java/com/freemark/demo/templates";
private static final String CLASS_PATH = "src/main/java/com/freemark/demo";
public static void main(String[] args) {
// 创建freeMarker配置实例
Configuration configuration = new Configuration();
Writer out = null;
try {
// 设置模版路径
configuration.setDirectoryForTemplateLoading(new File(TEMPLATE_PATH));
// 创建数据模型
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("packageName", "com.freemark.demo");
dataMap.put("className", "Test");
List<Column> columnList = new ArrayList<>();
Column idCol = Column.builder().type("Integer").name("id").build();
Column userNameCol = Column.builder().type("String").name("userName").build();
Column passwordCol = Column.builder().type("String").name("password").build();
columnList.add(idCol);
columnList.add(userNameCol);
columnList.add(passwordCol);
dataMap.put("colList", columnList);
// 加载模版文件
Template template = configuration.getTemplate("test.java.ftl");
// 生成文件流
File docFile = new File(CLASS_PATH "/" "Test.java");
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(docFile)));
// 输出到文件
template.process(dataMap, out);
System.out.println("Test.java 源码生成成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != out) {
out.flush();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
我们执行FreemarkerTest的main方法,生成Test.java文件如下:
package com.freemark.demo;
public class Test {
private Integer id;
private String userName;
private String password;
public Integer getId(){
return id;
}
public void setId(Integer id){
this.id=id;
}
public String getUserName(){
return userName;
}
public void setUserName(String userName){
this.userName=userName;
}
public String getPassword(){
return password;
}
public void setPassword(String password){
this.password=password;
}
}
四 总结
本篇对freemarker进行了深入一些的描述,并对上一篇的示例做了部分调整,使用了list、if等表达式、指令、插值这些freemarker的概念。
完整工程代码,可关注公众号回复freemarker引擎获取。