FreeMarker是一款免费的Java模板引擎,是一种基于模板和数据生成文本(HMLT、电子邮件、配置文件、源代码等)的工具,它不是面向最终用户的,而是一款程序员使用的组件。
FreeMarker最初设计是用来在MVC模式的Web开发中生成HTML页面的,所以没有绑定Servlet或任意Web相关的东西上,所以它可以运行在非Web应用环境中。
发展史
FreeMarker第一版在1999年未就发布了,2002年初使用JavaCC(Java Compiler Compiler是一个用Java开发的语法分析生成器)重写了FreeMarker的核心代码,2015年FreeMarker代码迁移到了Apache下。
GitHub地址:github.com/apache/free…
工作原理
FreeMarker模板存储在服务器上,当有用户访问的时候,FreeMarker会查询出相应的数据,替换模板中的标签,生成最终的HTML返回给用户,如下图:
基础使用分为3部分,这3部分组成了FreeMarker:
指令是FreeMarker用来识别转换的特殊标签,表达式是标签里具体的语法实现,其他部分是一些不好分类的模板。
使用FTL(freemarker template language)标签来调用指令。
指令速览:
下来我们分别来看每个指令对应具体使用。
assign 分为变量和代码片段声明两种。
可以是单变量声明,或多变量声明,下面是多变量声明的示例:
<#assign name="adam" age=18 "sex"="man">
${name} - ${age} - ${"sex"}
<#assign code>
<#list ["java","golang"] as c>
${c}
#list>
#assign>
${code}
其中 ${code} 是用来执行方法的,如果不调用话,代码片段不会执行。
效果如下:
attempt(尝试), recover(恢复)指令类似于程序的try catch,示例如下:
<#attempt>
i am ${name}
<#recover>
error name
#attempt>
如果有变量“name”就会正常显示,显示“i am xxx”,如果没有变量就会显示“error name”。
<#compress>
1 2 3 4 5
test only
I said, test only#compress>
1 2 3 4 5
test only
I said, test only
效果如下:
对空白不敏感的格式,移除空白行还是挺有用的功能。
<#escape x as x?html>
${firstName}
${lastName}
#escape>
上面的代码,类似于:
${firstName?html}
${lastName?html}
Java代码:
@RequestMapping("/freemarker")
public ModelAndView freemarker(ModelAndView modelAndView) {
modelAndView.addObject("firstName", "firstName");
modelAndView.addObject("lastName", "lastName");
modelAndView.setViewName("freemarker");
return modelAndView;
}
单问号后面跟的是操作函数,类似于Java中的方法名,html属于内建函数的一个,表示字符串会按照HTML标记输出,字符替换规则如下:
HTML代码:
<#escape x as x?html>
<#noescape>
${firstName}
#noescape>
${lastName}
#escape>
Java代码:
@RequestMapping("/freemarker")
public ModelAndView freemarker(ModelAndView modelAndView) {
modelAndView.addObject("firstName", "firstName");
modelAndView.addObject("lastName", "lastName");
modelAndView.setViewName("freemarker");
return modelAndView;
}
代码格式:
<#function name param1 param2 ... paramN>
...
<#return returnValue>
...
#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直接使用${}打印,会报模板错误(你可以自己试一下),
最终效果如下:
语法如下:
<#global name=value>
或
<#global name1=value1 name2=value2 ... nameN=valueN>
或
<#global name>
capture this
#global>
global使用和assign用法类似,只不过global声明是全局的,所有的命名空间都是可见的。
效果如下
语法如下:
<#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 or 2
#if>
语法:
<#import path as hash>
示例如下
freemarkerFooter.ftl 关键代码如下:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
this is footer.ftl<#assign copy="冯雪超的博客">
body>
html>
freemarker.ftl 关键代码如下:
<#import "freemarkerFooter.ftl" as footer>
${footer.copy}
语法:
<#include path>
示例如下
freemarkerFooterooter.ftl 代码如下:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
this is footer.ftl<#assign copy="冯雪超的博客">
body>
html>
freemarker.ftl 关键代码如下:
<#include "freemarkerFooter.ftl">
最终内容如下:
输出1-3的数字,如果等于2跳出循环,代码如下:
<#list 1..3 as n>
${n}
<#if n==2>
<#break>
#if>
#list>
注意:“1…3”等于[1,2,3]。
结果: 1 2
示例如下:
<#list 1..3>
<ul>
<#items as n>
<li>${n}li>
#items>
ul>
#list>
跳过最后一项
<#list 1..3 as n>
${n}
<#sep>,#sep>
#list>
最终结果:1 , 2 , 3
代码如下:
<#list 1..3 as n>
${n}
<#if !n_has_next>
最后一项
#if>
#list>
使用“变量_has_next”判断是否还有下一个选项,来找到最后一项,最终的结果:
1 2 3 最后一项
宏:是一个变量名的代码片段,例如:
<#macro sayhi name>
Hello, ${name}
#macro>
<@sayhi "Adam" />
相当于声明了一个名称为“sayhi”有一个参数“name”的宏,使用自定义标签“@”调用宏。
输出的结果: Hello, Adam
示例代码如下:
<#assign animal="dog" >
<#switch animal>
<#case "pig">
This is pig
<#break>
<#case "dog">
This is dog
<#break>
<#default>
This is Aaimal
#switch>
指令自动忽略空格特性
FreeMarker会忽略FTL标签中的空白标记,所以可以直接写:
<#list ["老王","老李","老张"]
as
p>
${p}
#list>
即使是这个格式也是没有任何问题的,FreeMarker会正常解析。但不推荐
字符拼接代码:
<#assign name="ABCDEFG">
${"Hello, ${name}"}
结果:Hello, ABCDEFG
算术符有五种:
示例代码:
${100 - 10 * 20}
输出:
-100
${1.999?int}
输出:
1
注意:数值转换不会进行四舍五入,会舍弃小数点之后的。
内建函数:相当于我们Java类里面的内置方法,非常常用,常用的内建函数有:时间内建函数、字符内建函数、数字内建函数等。
单问号:在FreeMarker中用单个问号,来调用内建函数,比如: ${“admin”?length} 查看字符串“admin”的字符长度,其中length就是字符串的内建函数。
双引号:表示用于判断值是否为null,比如:
<#if admin??>Admin is not null#if>
使用contains判断,代码示例:
<#if "admin"?contains("min")>
Min
<#else >
not min
#if>
输出:
min
示例代码:
<#assign name="Adam">
${name?uncap_first}
${name?upper_case}
${name?cap_first}
${name?lower_case}
输出:
adam ADAM Adam adam
更多的字符串内建函数:http://freemarker.foofun.cn/ref_builtins.html
示例代码:
${1.23569?string.percent}${1.23569?string["0.##"]}${1.23569?string["0.###"]}
输出:
124% 1.24 1.236
注意:
代码:
<#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
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
主要配置,如下:
#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.ftl
<html>
<head>
<meta charset="UTF-8">
<title>冯雪超的博客title>
head>
<body>
<div>
<#assign name="adam" age=18 "sex"="man">
${name} - ${age} - ${"sex"}
div>
<div>
<#assign code>
<#list ["java","golang"] as c>
${c}
#list>
#assign>
${code}
div>
<div>
<#attempt>
i am ${name}
<#recover>
error name
#attempt>
div>
<div>
<#compress>
1 2 3 4 5
test only
I said, test only#compress>
1 2 3 4 5
test only
I said, test only
div>
<div>
<div>
<#escape x as x?html>
<#noescape>
${firstName}
#noescape>
${lastName}
#escape>
div>
<div>
${firstName?html}
${lastName?html}
div>
div>
<div>
<#function sum x y z>
<#return x+y+z>
#function>
${sum(5,5,5)}
<#function wantToPrint>
这里的信息是显示不了的
#function>
<#if wantToPrint()??>
Message:${wantToPrint()}
#if>
div>
<div>
<#global name1="fxc1" name2="fxc2">
<#global name3>
capture this
#global>
<br>
${name1}<br>
${name2}<br>
${name3}<br>
div>
<div>
<#assign x=1 >
<#if x==1>
x is 1
<#elseif x==2>
x is 2
<#else>
x is not 1 or 2
#if>
div>
<div>
<#import "freemarkerFooter.ftl" as footer>
${footer.copy}
div>
<div>
<#include "freemarkerFooter.ftl">
div>
<div>
<#list 1..3 as n>
${n}
<#if n==2>
<#break>
#if>
#list>
div>
<dvi>
<#list 1..3 as n>
${n}
<#sep>,#sep>
#list>
dvi>
<div>
<#list 1..3 as n>
${n}
<#if !n_has_next>
最后一项
#if>
#list>
div>
<div>
<#macro sayhi name>
Hello, ${name}
#macro>
<@sayhi "Adam" />
div>
<div>
<#assign animal="dog" >
<#switch animal>
<#case "pig">
This is pig
<#break>
<#case "dog">
This is dog
<#break>
<#default>
This is Aaimal
#switch>
div>
<div>
<#list ["老王","老李","老张"]
as
p>
${p}
#list>
div>
<div>
<#if admin??>Admin is not null#if>
div>
<div>
<#if "admin"?contains("min")>
min
<#else >
not min
#if>
div>
<div>
<#assign name="Adam">
${name?uncap_first}
${name?upper_case}
${name?cap_first}
${name?lower_case}
div>
<div>
${1.23569?string.percent}${1.23569?string["0.##"]}${1.23569?string["0.###"]}
div>
<div>
<#assign timestamp=1534414202000>
${timestamp?number_to_datetime?string["yyyy/MM/dd HH:mm"]}
div>
<div>
<#assign nowTime = .now>${nowTime} <br />${nowTime?string["yyyy/MM/dd HH:mm"]} <br />
div>
body>
html>
FreemarkerFooter.ftl
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
this is footer.ftl<#assign copy="冯雪超的博客">
body>
html>
新建index.java文件,Application.java(入口文件)代码不便,index.java代码如下:
package com.littlefxc.blog.controller;
import lombok.Data;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author fengxuechao
*/
@Controller
public class IndexController {
@RequestMapping("/")
public ModelAndView index(ModelAndView modelAndView) {
modelAndView.setViewName("index");
modelAndView.addObject("names", Arrays.asList("小王", "小明", "小红"));
Goods goods = new Goods();
goods.setName("iMac");
goods.setPrice("¥20000");
goods.setCreateTime(new Date());
modelAndView.addObject("goods", goods);
return modelAndView;
}
@Data
public static class Goods{
private String name;
private String price;
private Date createTime;
}
@RequestMapping("/freemarker")
public ModelAndView freemarker(ModelAndView modelAndView) {
modelAndView.addObject("firstName", "firstName");
modelAndView.addObject("lastName", "lastName");
modelAndView.setViewName("freemarker");
return modelAndView;
}
}
关键代码解读:
1.@Controller注解:标识自己为控制器,只需要配置@RequestMapping之后,就可以把用户URL映射到控制器;
2.使用ModelAndView对象,指定视图名&添加视图对象。
执行上面4个步骤之后,就可以运行这个Java项目了,如果是IDEA使用默认快捷键“Shift + F10”启动调试,在页面访问:http://localhost:8080/freemarker 就可看到如下效果:
FreeMarker官方文档:freemarker.apache.org/
FreeMarker翻译的中文网站:freemarker.foofun.cn/toc.html