FreeMarker

FreeMarker

模板引擎,视图的模板是固定的,将模板中的数据修改后返回

模板 + 数据模型 = 输出

Hello World

SpringBoot整合FreeMarker

  1. 导包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  2. 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    spring:
    freemarker:
    charset: UTF-8
    # 指定模板文件后缀 freemarker template langurage
    suffix: .ftl
    # 指定模板所在路径
    template-loader-path: classpath:/templates
    cache: false
    allow-request-override: false
    check-template-location: true
    content-type: text/html
    expose-request-attributes: true
    expose-session-attributes: true
  3. 编写Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Controller
    public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("key1", "value1");
    map.put("key2", "value2");
    model.addAttribute("myMap", map);
    model.addAttribute("name", "World");
    return "hello";
    }
    }
  4. resources/templates下编写模板hello.ftl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!DOCTYPE html>
    <html>
    <head>
    <title>Hello FreeMarker</title>
    </head>
    <body>
    <h1>Hello ${name}!</h1>
    <p>Value for key1: ${myMap["key1"]}</p>
    <p>Value for key2: ${myMap["key2"]}</p>
    </body>
    </html>

运行,访问localhost:8080/hello

单独使用

  1. 导包、配置

  2. 编写模板welcome.ftl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome</title>
    </head>
    <body>
    <h1>Welcome ${name}!</h1>
    <p>Today is ${date?string("yyyy-MM-dd")}</p>
    <ul>
    <#list items as item>
    <li>${item}</li>
    </#list>
    </ul>
    </body>
    </html>
  3. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class FmTest {
    @Test
    public void test() throws Exception {
    Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
    // 设置模板所在路径和编码
    cfg.setClassLoaderForTemplateLoading(getClass().getClassLoader(), "/templates/");
    cfg.setDefaultEncoding("UTF-8");

    Map<String, Object> data = new HashMap<>();
    data.put("name", "John");
    data.put("date", new Date());
    data.put("items", Arrays.asList("Apple", "Banana", "Orange"));

    // 获取模板
    Template template = cfg.getTemplate("welcome.ftl");
    Writer out = new StringWriter();
    // 给数据渲染模板,输出到writer中
    template.process(data, out);

    String rendered = out.toString();
    System.out.println(rendered);
    }
    }

语法

插值/表达式

使用${..}来引入变量,如:${name}

字符串

\需要转义,或者在字符串前面加r

1
2
3
${"hello world"}
${"我的文件保存在C:\\盘"}
${r"我的文件保存在C:\盘"}

字符串拼接

1
2
<div>${"hello ${name}"}</div>
<div>${"hello "+ name}</div>

字符串截取

1
2
<div>${name[0]}${name[1]}</div>
<div>${name[0..2]}</div>

数字

  • 以钱的形式展示

    1
    2
    <#assign num=99>
    <div>${num?string.currency}</div>
  • 百分数

    1
    <div>${num?string.percent}</div>

布尔

1
2
<#assign flag=true>
<div>${flag?string("a","b")}</div>

集合

  • 直接定义然后输出

    1
    2
    3
    <#list ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"] as x>
    ${x}<br/>
    </#list>
  • 集合的元素可以是表达式、map

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <#list [2+2,"javaboy"] as x>
    ${x} <br/>
    </#list>

    <#list {"a":"a","b":"b"}?keys as x>
    ${x}
    </#list>
    <#list {"a":"a","b":"b"}?values as x>
    ${x}
    </#list>

运算符

算数运算

  1. = 或者 == 判断两个值是否相等。

  2. != 判断两个值是否不等。

  3. > 或者 gt 判断左边值是否大于右边值。

  4. >= 或者 gte 判断左边值是否大于等于右边值。

  5. < 或者 lt 判断左边值是否小于右边值。

  6. <= 或者 lte 判断左边值是否小于等于右边值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div>
    <#assign age=99>
    <#if age=99>age=99</#if>
    <#if age gt 99>age gt 99</#if>
    <#if age gte 99>age gte 99</#if>
    <#if age lt 99>age lt 99</#if>
    <#if age lte 99>age lte 99</#if>
    <#if age!=99>age!=99</#if>
    <#if age==99>age==99</#if>
    </div>

逻辑运算

  1. 逻辑与 &&

  2. 逻辑或 ||

  3. 逻辑非 !

    1
    2
    3
    4
    5
    6
    <div>
    <#assign age=99>
    <#if age=99 && 1==1> age=99 && 1==1 </#if>
    <#if age=99 || 1==0> age=99 || 1==0 </#if>
    <#if !(age gt 99)> !(age gt 99) </#if>
    </div>

空值处理

  • !指定空值的默认值,后面不写东西则为空字符串

  • ??判断某个变量是否存在

    1
    2
    3
    4
    5
    <div> ${aaa!"bbb"} </div>
    <div> ${aaa!} </div>
    <div>
    <#if aaa??> bbb </#if>
    </div>

变量

通过Model传值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Controller
public class HelloController {

@GetMapping("/hello")
public String hello(Model model) {
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User u = new User();
u.setUsername("name" + i);
u.setAddress("address" + i);
users.add(u);
}
Map<String, Object> info = new HashMap<>();
info.put("site", "site");
info.put("wechat", "wechat");
info.put("github", "github");
model.addAttribute("users", users);
model.addAttribute("info", info);
model.addAttribute("name", "myName");
return "hello";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello FreeMarker</title>
</head>
<body>
<#-- 普通变量 -->
<div>${name}</div>

<#-- 集合遍历 -->
<div>
<table border="1">
<#list users as u>
<tr>
<#-- 获取下标 -->
<td>${u_index}</td>
<td>${u.username}</td>
<td>${u.address}</td>
</tr>
</#list>
</table>
</div>
<#-- 集合遍历,指定范围 -->
<div>
<table border="1">
<#list users[3..5] as u>
<tr>
<td>${u.username}</td>
<td>${u.address}</td>
</tr>
</#list>
</table>
</div>
<#-- 指定输出集合中的元素 -->
<div>
${users[3].username}
</div>

<#-- Map直接获取值 -->
<div>${info.wechat}</div>
<div>${info['site']}</div>
<#-- Map获取所有key -->
<div>
<#list info?keys as key>
<div>${key} -- ${info[key]}</div>
</#list>
</div>
<#-- Map获取所有value -->
<div>
<#list info?values as value>
<div>${value}</div>
</#list>
</div>

</body>
</html>

内置函数和指令

常见内置函数

  • string函数:将一个非字符串值转换为字符串

  • number函数:将一个字符串或其他类型的值转换为数字

  • size函数:返回一个序列(如列表、数组、字符串等)的大小

    1
    <div>${users?size}</div>
  • default函数:返回第一个非空值

  • length函数:返回字符串的长度

  • upper_case函数:将字符串转换为大写形式

    1
    <div>${"hello"?upper_case}</div>
  • lower_case函数:将字符串转换为小写形式

    1
    <div>${"HELLO"?lower_case}</div>
  • cap_first函数:首字母大写

    1
    <div>${"hello"?cap_first}</div>
  • trim:去掉前后空格

    1
    <div>${" hello "?trim}</div>
  • 日期转换

    1
    <div>${birthday?string("yyyy-MM-dd")}</div>

常见内置指令

  • #if指令:用于条件判断,根据条件判断结果执行相应的操作

    1
    2
    3
    4
    5
    6
    7
    8
    <div>
    <#assign age=23>
    <#if (age>60)>老年人
    <#elseif (age>40)>中年人
    <#elseif (age>20)>青年人
    <#else> 少年人
    </#if>
    </div>
  • #list指令:用于遍历集合(如数组、列表)并对其中每个元素执行操作

  • #assign指令:用于给变量赋值

  • #macro指令:用于定义宏(类似于函数)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <#macro book bs>
    <table border="1">
    <#list bs as b>
    <tr>
    <td>${b}</td>
    </tr>
    </#list>
    </table>
    </#macro>
    <@book ["三国演义","水浒传"]/>

    可以使用<#nested>作为占位符,调用宏的时候标签里的内容会填充到占位符的位置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <#macro book bs>
    <table border="1">
    <#list bs as b>
    <tr>
    <td>${b}</td>
    </tr>
    </#list>
    </table>
    <#nested>
    </#macro>
    <@book ["三国演义","水浒传"]>
    <h1>hello javaboy!</h1>
    </@book>
  • #include指令:用于在模板中引入其他模板

    1
    <#include "./about.ftl">
  • #import指令:导入外部的模板

    about.ftl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <#macro book bs>
    <table border="1">
    <#list bs as b>
    <tr>
    <td>${b}</td>
    </tr>
    </#list>
    </table>
    <#nested>
    </#macro>

    hello.ftl

    1
    2
    3
    4
    5
    <#import "./about.ftl" as about>

    <@about.book ["三国演义","水浒传"]>
    <h1>hello javaboy!</h1>
    </@about.book>
  • #noparse指令:框起来的FreeMarker语法不会被渲染

常见内置变量

  • .now:返回当前时间
  • .size:返回一个序列(如列表、数组、字符串等)的大小
  • .length:返回字符串的长度

SpringBoot整合FreeMarker

自动配置

首先导包

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration为FreeMarker的自动配置类

1
2
3
4
5
6
@AutoConfiguration
@ConditionalOnClass({Configuration.class, FreeMarkerConfigurationFactory.class})
@EnableConfigurationProperties({FreeMarkerProperties.class})
@Import({FreeMarkerServletWebConfiguration.class, FreeMarkerReactiveWebConfiguration.class, FreeMarkerNonWebConfiguration.class})
public class FreeMarkerAutoConfiguration {
...
  • @ConditionalOnClass({Configuration.class, FreeMarkerConfigurationFactory.class})

    当导入FreeMarker后,就会存在ConfigurationFreeMarkerConfigurationFactory这两个类,自动配置类也就生效了

  • @Import({FreeMarkerServletWebConfiguration.class,

    剩下的配置在类FreeMarkerServletWebConfiguration

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Configuration(
    proxyBeanMethods = false
    )
    @ConditionalOnWebApplication(
    type = Type.SERVLET
    )
    @ConditionalOnClass({Servlet.class, FreeMarkerConfigurer.class})
    @AutoConfigureAfter({WebMvcAutoConfiguration.class})
    class FreeMarkerServletWebConfiguration extends AbstractFreeMarkerConfiguration {
    ...
    • @ConditionalOnWebApplication(type = Type.SERVLET):配置在web环境下生效

    • @AutoConfigureAfter({WebMvcAutoConfiguration.class}):自动化配置在WebMvcAutoConfiguration之后完成

    • 这个配置类主要提供了ConfigurationfreeMarkerViewResolverConfiguration有一些基本配置,如编码、模板所在位置等;freeMarkerViewResolver是视图解析器,配置有后缀等属性

    • 这个配置类的构造方法注入了FreeMarkerProperties,里面有一些默认的配置信息

      1
      2
      3
      4
      5
      6
      7
      8
      public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {
      public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";
      public static final String DEFAULT_PREFIX = "";
      public static final String DEFAULT_SUFFIX = ".ftlh";
      private Map<String, String> settings = new HashMap();
      private String[] templateLoaderPath = new String[]{"classpath:/templates/"};
      private boolean preferFileSystemAccess;
      ...

主要配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
spring:
freemarker:
# 模板编码
charset: UTF-8
# 模板后缀
suffix: .ftl
# 模板位置
template-loader-path: classpath:/templates
# 是否开启缓存
cache: false
# HttpServletRequest的属性是否可以覆盖controller中model的同名项
allow-request-override: false
# HttpSession的属性是否可以覆盖controller中model的同名项
allow-session-override: false
# 是否检查模板位置
check-template-location: true
# Content-Type的值
content-type: text/html
# 是否将HttpServletRequest中的属性添加到Model中
expose-request-attributes: true
# 是否将HttpSession中的属性添加到Model中
expose-session-attributes: true

使用

创建User类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class User {
private Long id;
private String username;
private String address;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}
}

创建UserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller
public class UserController {
@GetMapping("/index")
public String index(Model model) {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setId((long) i);
user.setUsername("name" + i);
user.setAddress("address" + i);
users.add(user);
}
model.addAttribute("users", users);
return "index";
}
}

创建index.ftl视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1">
<tr>
<td>用户编号</td>
<td>用户名称</td>
<td>用户地址</td>
</tr>
<#list users as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.address}</td>
</tr>
</#list>
</table>
</body>
</html>

运行,访问localhost:8080/index

静态输出文件

步骤:

  1. 指定模板文件
  2. 准备数据
  3. 将数据和模板结合,输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello ${name!''} <br>
<hr>
<b>对象Student中的数据展示:</b><br/>
姓名:${stu.name}<br/>
年龄:${stu.age}
<hr>

${date?string('yyyy年MM月dd日')}

<#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
<#assign data=text?eval />
开户行:${data.bank} 账号:${data.account}
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@SpringBootTest
public class FreeMarkerTest {
@Resource
private Configuration configuration;

@Test
public void test() throws IOException, TemplateException {
Template template = configuration.getTemplate("01-basic.ftl");
// 输出静态文件
template.process(getData(), new FileWriter("d:/list.html"));
}

private Map getData() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "freemarker");
map.put("date", new Date());
Student student = new Student();
student.setName("zs");
student.setAge(18);
map.put("stu", student);
return map;
}
}

FreeMarker
http://xwww12.github.io/2023/05/03/框架/FreeMarker/
作者
xw
发布于
2023年5月3日
许可协议