一、freemarker介绍

  • FreeMarker 是一款 模板引擎
    • 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件
  • 模板编写为FreeMarker Template Language(FTL)
    • 它是简单的,专用的语言,不是像PHP那样成熟的变成语言。那就意味着要准备数据在在真实编程语言中来显示,比如数据库查询和业务运算,之后模板显示以及准备好的数据。在模板中,可以更加专注于如何展现数据,而在模板之外可以专注于要展示什么数据

二、freemarker环境搭建&&快速入门

需要创建Spring Boot + Freemarker 工程用于测试模板

2.1、创建测试工程

  • 创建一个freemark-demo的测试工程用于freemarker的功能测试与模板测试

  • POM文件如下所示

    • <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
      
          <groupId>com.coolman</groupId>
          <artifactId>freemarker-demo</artifactId>
          <version>1.0-SNAPSHOT</version>
      
          <properties>
              <maven.compiler.source>8</maven.compiler.source>
              <maven.compiler.target>8</maven.compiler.target>
          </properties>
          <dependencies>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
              <!--freemarker springboot starter-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-freemarker</artifactId>
              </dependency>
      
              <!-- lombok -->
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
              </dependency>
          </dependencies>
      </project>
      

2.2、配置文件

  • application.yml文件如下所示

    • server:
        port: 9527
      spring:
        application:
          name: freemarker-demo # 指定服务名
        freemarker:
          cache: false  # 关闭缓存,方便测试
          settings:
            template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
          suffix: .ftl               #指定Freemarker模板文件的后缀名
          template-loader-path: classpath:/templates  #指定模板文件存放的位置
      

2.3、创建启动类

  • 启动类如下所示

    • package com.coolman.freemarker;
      
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      
      @SpringBootApplication
      public class FreemarkerApplication {
          public static void main(String[] args) {
              SpringApplication.run(FreemarkerApplication.class, args);
          }
      }
      
      

2.4、创建模型类

  • 在freemarker的测试工程下创建模型类型用于测试

    • package com.coolman.freemarker.model;
      
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class Person {
      
          private String name;
          private Integer age;
      }
      
      

2.5、创建模板

  • 在resources下创建templates,并创建模板文件freemarker.ftl文件(模板中的插值表达式最终会被freemarker替换成具体的数据)

    • 编写一个简单的HTML模板

    • <!DOCTYPE html>
      <html>
      <head>
          <meta charset="utf-8">
          <title>Hello Freemarker!</title>
      </head>
      <body>
      <b>普通文本 String 展示:</b><br><br>
      Welcome to SuperCoolMan's home !! <br>
      I’m 99 years old this year !! <br>
      <hr>
      <hr>
      </body>
      </html>
      

2.6、创建测试类并测试

  • 创建Controller类,添加数据,最后返回模板文件

    • 测试1:

      • package com.coolman.freemarker.controller;
        
        import freemarker.template.Configuration;
        import freemarker.template.Template;
        import freemarker.template.TemplateException;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RestController;
        
        import java.io.FileWriter;
        import java.io.IOException;
        import java.util.HashMap;
        
        @RestController
        @RequestMapping("/freemarker")
        public class FreemarkerController {
        
            // 引入freemarker.template包下的Configuration类,用来获取指定的模板对象
            @Autowired
            private Configuration configuration;
        
            /**
             * 通过模板文件,生成html文件
             */
            @GetMapping("/test")
            public String getHtml() {
                try {
                    // 使用Configuration的getTemplate方法,指定模板文件,获取模板对象
                    Template template = configuration.getTemplate("HelloFreemarker.ftl");
        
                    // 绑定数据
                    // 数据1:
                    HashMap<String, Object> map = new HashMap<>();
                    map.put("name", "SuperCoolMan");
                    map.put("age", 99);
        
                    FileWriter fileWriter = new FileWriter("E:\\系统默认\\桌面\\news-init\\freemarker-demo\\src\\main\\resources\\testHtml\\HelloFreemarker.html");
                    template.process(map, fileWriter);
        
                    return "SUCCESS!";
                } catch (IOException | TemplateException e) {
                    e.printStackTrace();
                }
                return "ERROR!";
            }
        }
        
        
      • 浏览器打开生成的静态文件

  • 修改Controller中的方法,添加Person对象数据

    • package com.coolman.freemarker.controller;
      
      import com.coolman.freemarker.model.Person;
      import freemarker.template.Configuration;
      import freemarker.template.Template;
      import freemarker.template.TemplateException;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.io.FileWriter;
      import java.io.IOException;
      import java.util.HashMap;
      
      @RestController
      @RequestMapping("/freemarker")
      public class FreemarkerController {
      
          // 引入freemarker.template包下的Configuration类,用来获取指定的模板对象
          @Autowired
          private Configuration configuration;
      
          /**
           * 通过模板文件,生成html文件
           */
          @GetMapping("/test")
          public String getHtml() {
              try {
                  // 使用Configuration的getTemplate方法,指定模板文件,获取模板对象
                  Template template = configuration.getTemplate("HelloFreemarker.ftl");
      
                  // 绑定数据
                  // 数据1:
                  HashMap<String, Object> map = new HashMap<>();
                  map.put("name", "SuperCoolMan");
                  map.put("age", 99);
      
                  // 数据2:
                  map.put("person", new Person("超级猛男", 18));
      
                  FileWriter fileWriter = new FileWriter("E:\\系统默认\\桌面\\news-init\\freemarker-demo\\src\\main\\resources\\testHtml\\HelloFreemarker.html");
                  template.process(map, fileWriter);
      
                  return "SUCCESS!";
              } catch (IOException | TemplateException e) {
                  e.printStackTrace();
              }
              return "ERROR!";
          }
      }
      
      
    • 修改模板

      • <!DOCTYPE html>
        <html>
        <head>
            <meta charset="utf-8">
            <title>Hello Freemarker!</title>
        </head>
        <body>
        <b>普通文本 String 展示:</b><br><br>
        Welcome to ${name}'s home !! <br>
        I’m ${age} years old this year !! <br>
        <hr>
        <b>对象Person中的数据展示:</b><br/>
        姓名:${person.name}<br/>
        年龄:${person.age}
        <hr>
        </body>
        </html>
        
    • 浏览器打开生成的静态文件

2.7、总结

三、freemarker语法指令

3.1、基础语法

3.1.1、注释

  • 注释,即<#-- -->,介于其之间的内容会被freemarker忽略

    • <#-- 我是一个freemarker的注释-->
      

3.1.2、插值

  • 插值(Interpolation):即${...}部分,freemarker会用真实的值代替${}(PS:如果返回的数据中没有该真实值,freemarker服务会直接报错)

    • Welcome to ${name}'s home
      

3.1.3、FTL指令

  • FTL指令:和HTML标记类似,名字前加#予以区分,Freemarker会解析标签中的表达式或逻辑

    • <# > FTL指令 </#>
      

3.1.4、文本

  • 文本,仅文本信息,这些不是freemarker的注释、插值、FTL指令的内容会被freemarker忽略解析,直接输出内容

    • <#--freemarker中的普通文本-->
      我是一个普通的文本
      

3.2、集合指令(List、Map)

接下来的测试,只截取部分代码,直接在原代码的基础上增加即可

  • 为了Controller尽可能简略,创建一个getData方法,返回一个Map集合,Controller方法直接调用这个getData生成测试数据即可

    •     private Map<String, Object> getData() {
              HashMap<String, Object> map = new HashMap<>();
              // String类型:
              map.put("name", "SuperCoolMan");
              map.put("age", 99);
      
              // Person类型:
              map.put("person", new Person("超级猛男", 18));
              
              // 
              return map;
          }
      

3.2.1、List集合

  • Controller

    • // List集合类型
              ArrayList<Person> list = new ArrayList<>();
              list.add(new Person("瞳夕", 18));
              list.add(new Person("二呆", 18));
              list.add(new Person("嘿嘿", 18));
              map.put("wifeList", list);
      
  • 模板

    • <b>List类型中的数据展示:</b><br/>
          <#list wifeList as wife>
              序号:${wife_index + 1} <br/>   <#--freemarker中FTL指令自带的序号属性,默认从0开始-->
              姓名:${wife.name} <br/>
              年龄:${wife.age}  <br/><br/>
          </#list>
      <hr>
      
  • 最终效果

3.2.2、Map集合

  • Controller

    • // Map集合类型
              HashMap<String, Object> anOtherMap = new HashMap<>();
              anOtherMap.put("smallWife1", new Person("莫妮卡", 18));
              anOtherMap.put("smallWife2", new Person("玛丽莲梦露", 18));
              map.put("smallWife", anOtherMap);
      
  • 模板

    • <b>Map + Person 类型中的数据展示:</b><br/>
          <b>方式一:通过map['keyname'].property 输出对应信息:</b><br/>
              性别:${wifeMap['smallWife1'].name} <br/>
              年龄:${wifeMap['smallWife1'].age} <br/>
          <b>方式二:通过map.keyname.property 输出对应信息:</b><br/>
              性别:${wifeMap.smallWife2.name} <br/>
              年龄:${wifeMap.smallWife2.age} <br/>
      <hr>
      
  • 最终效果

  • 遍历Map集合

    • 模板

      • <b>遍历Map中多个Person信息:</b><br/>
            <#list wifeMap?keys as key>
                序号:${key_index} <br/>
                性别:${key.name} <br/>
                年龄:${key.age} <br/><br/>
            </#list>
        <hr>
        
    • 最终效果

3.3、if指令

  • if指令即判断指令,是常用的FTL指令,freemarker在解析的时候遇到if会进行判断,条件为真则输出if中间的内容,否则跳过内容不在输出

    • 指令格式

      • <#if >...</#if>
        
  • Controller

    • // if test
      map.put("score", 88);
      
  • 模板

    • <b>if指令测试:</b><br/>
          <#if (score > 90)>
              继续努力
          <#elseif (score > 80)>
              面包总会有的
          <#else>
              革命尚未成功,同志仍需努力
          </#if>
      <hr>
      
  • 最终效果

3.4、空值处理

3.4.1、判断某变量是否存在

  • 语法格式

    • variable??
  • 如果该变量存在,返回true,否则返回false

  • 如下例所示

    • <#if stus??>
          <#list stus as stu>
          ......
          </#list>
      </#if>
      

3.4.2、缺失变量默认值

  • 使用!要指定一个默认值,当变量为空的时候显示默认值
    • 例:${name!'默认值'}表示如果name为空,显示默认值
  • 如果是嵌套对象则建议使用()括起来
    • 例:${(stu.bestFriend.name)!"默认值"}表示如果stu或者bestFriend或者name为空默认显示默认值

3.5、内建函数

3.5.1、获取集合大小

  • 模板中直接取值
    • ${集合名?size}

3.5.2、日期格式化

  • 后端返回一个Date对象,模板要取值,则如下所示
    • map.put("today",new Date())
    • 显示年月日
      • ${today?date}
    • 显示时分秒
      • ${today?time}
    • 显示日期+时间
      • ${today?datetime}
    • 自定义格式化
      • ${today?string("yyyy年MM月")}

3.5.3、内建函数c(数字格式化)

  • 后端返回一串数字,模板可以对其进行格式化,如下所示
    • map.put("point", 102920122);
  • point是数字型,使用${point}会显示这个数字的值,每三位使用逗号隔开
  • 如果不像显示为每三位分隔的数字,可以使用c函数将数字转成字符串输出
  • 指令格式如下所示
    • ${point?c}