写点什么

低代码探索:freemarker 的模板和表达式

  • 2022-10-16
    北京
  • 本文字数:3719 字

    阅读完需:约 1 分钟

低代码探索:freemarker的模板和表达式

系列文章:

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} 或 ${NAME} 也不相同。

2、插值 仅仅可以在 文本(或字符串表达式)中使用。

3、FTL 标签 不可以在其他 FTL 标签 和 插值中使用。比如, 这样做是 错误 的: <#if <#include 'foo'>='bar'>...</#if>

4、注释 可以放在 FTL 标签 和 插值中。示例:

<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:

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 实体类定义

package com.freemark.demo.entity;
import lombok.Builder;import lombok.Data;
@Data@Builderpublic class Column { private String type; private String name;}
复制代码

Column 用于封装参数,有 type 和 name 两个属性。 这里为了简化代码 引入了 lombok,在 pom.xml 中增加相关依赖引入即可:

<dependency>	<groupId>org.projectlombok</groupId>	<artifactId>lombok</artifactId>	<version>1.18.22</version></dependency>
复制代码

3.3 生成代码方法

主要就是完成参数封装,模板加载和生成方法与上一篇内容相似。

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 引擎获取。

发布于: 刚刚阅读数: 4
用户头像

磨炼中成长,痛苦中前行 2017-10-22 加入

微信公众号【程序员架构进阶】。多年项目实践,架构设计经验。曲折中向前,分享经验和教训

评论

发布
暂无评论
低代码探索:freemarker的模板和表达式_低代码平台_程序员架构进阶_InfoQ写作社区