乐趣区

Spring Boot 最佳实践(三)模板引擎FreeMarker集成

一、FreeMaker 介绍
FreeMarker 是一款免费的 Java 模板引擎,是一种基于模板和数据生成文本(HMLT、电子邮件、配置文件、源代码等)的工具,它不是面向最终用户的,而是一款程序员使用的组件。
FreeMarker 最初设计是用来在 MVC 模式的 Web 开发中生成 HTML 页面的,所以没有绑定 Servlet 或任意 Web 相关的东西上,所以它可以运行在非 Web 应用环境中。
发展史
FreeMarker 第一版在 1999 年未就发布了,2002 年初使用 JavaCC(Java Compiler Compiler 是一个用 Java 开发的语法分析生成器)重写了 FreeMarker 的核心代码,2015 年 FreeMarker 代码迁移到了 Apache 下。
GitHub 地址:https://github.com/apache/fre…
工作原理
FreeMarker 模板存储在服务器上,当有用户访问的时候,FreeMarker 会查询出相应的数据,替换模板中的标签,生成最终的 HTML 返回给用户,如下图:

二、FreeMarker 基础使用
基础使用分为 3 部分,这 3 部分组成了 FreeMarker:

指令
表达式

指令是 FreeMarker 用来识别转换的特殊标签,表达式是标签里具体的语法实现,其他部分是一些不好分类的模板。
2.1 指令
使用 FTL(freemarker template language)标签来调用指令。
指令速览:

assign
attempt, recover
compress
escape, noescape
flush
ftl
function, return
global
if, else, elseif
import
include
list, else, items, sep, break
local
macro, nested, return
noparse
nt
setting
stop
switch, case, default, break
t, lt, rt
visit, recurse, fallback
用户自定义标签

下来我们分别来看每个指令对应具体使用。
2.1.1 assign 代码声明
assign 分为变量和代码片段声明两种。
2.1.1.1 变量声明
可以是单变量声明,或多变量声明,下面是多变量声明的示例:
<#assign name=”adam” age=18 “sex”=”man”>
${name} – ${age} – ${“sex”}
单个变量的话,只写一个就可以了。
2.1.1.2 代码片段声明
<#assign code>
<#list [“java”,”golang”] as c>
${c}
</#list>
</#assign>
${code}
其中 ${code} 是用来执行方法的,如果不调用话,代码片段不会执行。
2.1.2 attempt, recover 异常指令
attempt(尝试), recover(恢复)指令类似于程序的 try catch,示例如下:
<#attempt>
i am ${name}
<#recover>
error name
</#attempt>
如果有变量“name”就会正常显示,显示“i am xxx”,如果没有变量就会显示“error name”。
2.1.3 compress 压缩代码移除空白行
<#compress>
1 2 3 4 5

test only

I said, test only
</#compress>

1 2 3 4 5

test only

I said, test only
效果如下:

对空白不敏感的格式,移除空白行还是挺有用的功能。
2.1.4 escape, noescape 转义,不转义
2.1.4.1 escape 使用
<#escape x as x?html>
${firstName}
${lastName}
</#escape>
上面的代码,类似于:
${firstName?html}
${lastName?html}
Java 代码:
@RequestMapping(“/”)
public ModelAndView index() {
ModelAndView modelAndView = new ModelAndView(“/index”);
modelAndView.addObject(“firstName”, “<span style=’color:red’>firstName</span>”);
modelAndView.addObject(“lastName”, “lastName”);
return modelAndView;
}
最终的效果是:

2.1.4.2“?html”语法解析
单问号后面跟的是操作函数,类似于 Java 中的方法名,html 属于内建函数的一个,表示字符串会按照 HTML 标记输出,字符替换规则如下:

< 替换为 &lt;

> 替换为 &gt;

& 替换为 &amp;

” 替换为 &quot;

2.1.4.3 noescape 使用
HTML 代码:
<#escape x as x?html>
<#noescape>
${firstName}
</#noescape>
${lastName}
</#escape>
Java 代码:
@RequestMapping(“/”)
public ModelAndView index() {
ModelAndView modelAndView = new ModelAndView(“/index”);
modelAndView.addObject(“firstName”, “<span style=’color:red’>firstName</span>”);
modelAndView.addObject(“lastName”, “lastName”);
return modelAndView;
}
最终效果:

2.1.5 function, return 方法声明
代码格式:
<#function name param1 param2 … paramN>

<#return returnValue>

</#function>

name 为方法名称
param1, param2,paramN 方法传递过来的参数,可以有无限个参数,或者没有任何参数
return 方法返回的值,可以出现在 function 的任何位置和出现任意次数

示例代码如下:
<#function sum x y z>
<#return x+y+z>
</#function>

${sum(5,5,5)}
注意:function 如果没有 return 是没有意义的,相当于返回 null,而 function 之中信息是不会打印到页面的,示例如下:
<#function wantToPrint>
这里的信息是显示不了的
</#function>

<#if wantToPrint()??>
Message:${wantToPrint()}
</#if>
“??”用于判断值是否是 null,如果为 null 是不执行的。如果不判 null 直接使用 ${} 打印,会报模板错误,效果如下:

2.1.6 global 全局代码声明
语法如下:
<#global name=value>

<#global name1=value1 name2=value2 … nameN=valueN>

<#global name>
capture this
</#global>
global 使用和 assign 用法类似,只不过 global 声明是全局的,所有的命名空间都是可见的。
2.1.7 if elseif else 条件判断
语法如下:
<#if condition>

<#elseif condition2>

<#elseif condition3>


<#else>

</#if>
示例如下:
<#assign x=1 >
<#if x==1>
x is 1
<#elseif x==2>
x is 2
<#else>
x is not 1
</#if>
2.1.8 import 引入模板
语法:<#import path as hash>
示例如下
footer.ftl 代码如下:
<html>
<head>
<title> 王磊的博客 </title>
</head>
<body>
this is footer.ftl
<#assign copy=” 来自 王磊的博客 ”>
</body>
</html>
index.ftl 代码如下:
<html>
<head>
<title> 王磊的博客 </title>
</head>
<body>
<#import “footer.ftl” as footer>
${footer.copy}
</body>
</html>
最终输出内容:
来自 王磊的博客
2.1.9 include 嵌入模板
语法:<#include path>
示例如下
footer.ftl 代码如下:
<html>
<head>
<title> 王磊的博客 </title>
</head>
<body>
this is footer.ftl
<#assign copy=” 来自 王磊的博客 ”>
</body>
</html>
index.ftl 代码如下:
<html>
<head>
<title> 王磊的博客 </title>
</head>
<body>
<#include “footer.ftl”>
</body>
</html>
最终内容如下:
this is footer.ftl
2.1.10 list, else, items, sep, break 循环
2.1.10.1 正常循环
输出 1 - 3 的数字,如果等于 2 跳出循环,代码如下:
<#list 1..3 as n>
${n}
<#if n==2>
<#break>
</#if>
</#list>
注意:“1..3”等于 [1,2,3]。
结果:1 2
2.1.10.2 使用 items 输出
示例如下:
<#list 1..3>
<ul>
<#items as n>
<li>${n}</li>
</#items>
</ul>
</#list>
2.1.10.3 sep 使用
跳过最后一项
<#list 1..3 as n>
${n}
<#sep>,</#sep>
</#list>
最终结果:1 , 2 , 3
2.1.10.4 数组最后一项
代码如下:
<#list 1..3 as n>
${n}
<#if !n_has_next>
最后一项
</#if>
</#list>
使用“变量_has_next”判断是否还有下一个选项,来找到最后一项,最终的结果:1 2 3 最后一项
2.1.11 macro 宏
宏:是一个变量名的代码片段,例如:
<#macro sayhi name>
Hello, ${name}
</#macro>

<@sayhi “Adam” />
相当于声明了一个名称为“sayhi”有一个参数“name”的宏,使用自定义标签“@”调用宏。
输出的结果:Hello, Adam
2.1.12 switch, case, defalut, break 多条件判断
示例代码如下:
<#assign animal=”dog” >
<#switch animal>
<#case “pig”>
This is pig
<#break>
<#case “dog”>
This is dog
<#break>
<#default>
This is Aaimal
</#switch>
2.1.13 扩展知识
指令自动忽略空格特性
FreeMarker 会忽略 FTL 标签中的空白标记,所以可以直接写:
<#list [“ 老王 ”,” 老李 ”,” 老张 ”]
as
p>
${p}
</#list>
即使是这个格式也是没有任何问题的,FreeMarker 会正常解析。
2.2 表达式
2.2.1 字符串拼接
字符拼接代码:
<#assign name=”ABCDEFG”>
${“Hello, ${name}”}
结果:Hello, ABCDEFG
2.2.2 算术运算
2.2.2.1 算术符
算术符有五种:

+

*
/

% 求余(求模)

示例代码:
${100 – 10 * 20}
输出:
-100
2.2.2.2 数值转换
${1.999?int}
输出:
1
注意:数值转换不会进行四舍五入,会舍弃小数点之后的。
2.2.3 内建函数(重点)
内建函数:相当于我们 Java 类里面的内置方法,非常常用,常用的内建函数有:时间内建函数、字符内建函数、数字内建函数等。
2.2.3.1 单个问号和两个问号的使用和区别
单问号:在 FreeMarker 中用单个问号,来调用内建函数,比如:${“admin”?length} 查看字符串“admin”的字符长度,其中 length 就是字符串的内建函数。
双引号:表示用于判断值是否为 null,比如:
<#if admin??>
Admin is not null
</#if>
2.2.3.2 字符串内建函数
2.2.3.2.1 是否包含判断
使用 contains 判断,代码示例:
<#if “admin”?contains(“min”)>
min
<#else >
not min
</#if>
输出:
min
2.2.3.2.2 大小写转换
示例代码:
<#assign name=”Adam”>
${name?uncap_first}
${name?upper_case}
${name?cap_first}
${name?lower_case}
输出:
adam ADAM Adam adam
更多的字符串内建函数:https://freemarker.apache.org…
2.2.3.3 数字内建函数
示例代码:
${1.23569?string.percent}
${1.23569?string[“0.##”]}
${1.23569?string[“0.###”]}
输出:
124% 1.24 1.236
注意:

使用 string.percent 计算百分比,会自动四舍五入。
使用“?string[“0.##”]”可以自定义取小数点后几位,会自动四舍五入。

2.2.3.4 时间内建函数
2.2.3.4.1 时间戳转换为任何时间格式
代码:
<#assign timestamp=1534414202000>
${timestamp?number_to_datetime?string[“yyyy/MM/dd HH:mm”]}
输出:
2018/08/16 18:10
2.2.3.4.2 时间格式化
示例代码:
<#assign nowTime = .now>
${nowTime} <br />
${nowTime?string[“yyyy/MM/dd HH:mm”]} <br />
输出:
2018-8-16 18:33:50
2018/08/16 18:33
更多内建方法:https://freemarker.apache.org…
三、Spring Boot 集成
3.1 集成环境

Spring Boot 2.0.4
FreeMaker 2.3.28
JDK 8
Windows 10
IDEA 2018.2.1

3.2 集成步骤
3.2.1 pom.xml 添加 FreeMaker 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
3.2.2 application.properties 配置模板
主要配置,如下:
## Freemarker 配置
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.cache=false
spring.freemarker.charset=UTF-8
spring.freemarker.check-template-location=true
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.request-context-attribute=request
spring.freemarker.suffix=.ftl

配置项
类型
默认值
建议值
说明

spring.freemarker.template-loader-path
String
classpath:/templates/
默认
模版存放路径

spring.freemarker.cache
bool
true
默认
是否开启缓存,生成环境建议开启

spring.freemarker.charset
String

UTF-8
编码

spring.freemarker.content-type
String
text/html
text/html
content-type 类型

spring.freemarker.suffix
String
.ftl
.ftl
模板后缀

spring.freemarker.expose-request-attributes
bool
false
false
设定所有 request 的属性在 merge 到模板的时候,是否要都添加到 model 中

spring.freemarker.expose-session-attributes
bool
false
false
设定所有 HttpSession 的属性在 merge 到模板的时候,是否要都添加到 model 中.

spring.freemarker.request-context-attribute
String

request
RequestContext 属性的名称

更多配置:
# FREEMARKER (FreeMarkerProperties)
spring.freemarker.allow-request-override=false # Whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.allow-session-override=false # Whether HttpSession attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.cache=false # Whether to enable template caching.
spring.freemarker.charset=UTF-8 # Template encoding.
spring.freemarker.check-template-location=true # Whether to check that the templates location exists.
spring.freemarker.content-type=text/html # Content-Type value.
spring.freemarker.enabled=true # Whether to enable MVC view resolution for this technology.
spring.freemarker.expose-request-attributes=false # Whether all request attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-session-attributes=false # Whether all HttpSession attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-spring-macro-helpers=true # Whether to expose a RequestContext for use by Spring’s macro library, under the name “springMacroRequestContext”.
spring.freemarker.prefer-file-system-access=true # Whether to prefer file system access for template loading. File system access enables hot detection of template changes.
spring.freemarker.prefix= # Prefix that gets prepended to view names when building a URL.
spring.freemarker.request-context-attribute= # Name of the RequestContext attribute for all views.
spring.freemarker.settings.*= # Well-known FreeMarker keys which are passed to FreeMarker’s Configuration.
spring.freemarker.suffix=.ftl # Suffix that gets appended to view names when building a URL.
spring.freemarker.template-loader-path=classpath:/templates/ # Comma-separated list of template paths.
spring.freemarker.view-names= # White list of view names that can be resolved.
3.2.3 编写 HTML 代码
<html>
<head>
<title> 王磊的博客 </title>
</head>
<body>
<div>
Hello,${name}
</div>
</body>
</html>
3.2.4 编写 Java 代码
新建 index.java 文件,Application.java(入口文件)代码不便,index.java 代码如下:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping(“/”)
public class Index {
@RequestMapping(“/”)
public ModelAndView index() {
ModelAndView modelAndView = new ModelAndView(“/index”);
modelAndView.addObject(“name”, “ 老王 ”);
return modelAndView;
}
}

关键代码解读:

@Controller 注解:标识自己为控制器,只需要配置 @RequestMapping 之后,就可以把用户 URL 映射到控制器;
使用 ModelAndView 对象,指定视图名 & 添加视图对象。

3.2.5 运行
执行上面 4 个步骤之后,就可以运行这个 Java 项目了,如果是 IDEA 使用默认快捷键“Shift + F10”启动调试,在页面访问:http://localhost:8080/ 就可看到如下效果:

四、参考资料
FreeMarker 官方文档:https://freemarker.apache.org/
FreeMarker 翻译的中文网站:http://freemarker.foofun.cn/t…

退出移动版