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

2022-12-01 15:46:40 浏览数 (1)

系列文章:

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.582y=4,那么常用的插值表达式和对应的取值如下面所示:
代码语言:javascript复制
#{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引擎获取。

0 人点赞