Freemarker是什么?
Freemarker是一个强大的 Java 模板引擎,它允许开发人员将展示层与应用逻辑分离。通过提供一种灵活高效的方式,在运行时生成动态内容。Freemarker模板使用类似HTML的语法,但包含可以在运行时填充数据的占位符和控制结构。这使得创建动态网页、电子邮件和其他文本文档非常简单。
Freemarker支持条件语句、循环、宏等各种功能,并且可以与不同的数据模型和框架集成。它在Java Web开发中被广泛应用于生成HTML视图、电子邮件模板和其他文本输出。
个人理解:Freemarker是类似于JSP的模板引擎,只要我们理解了什么是模板引擎就知道Freemarker大概是一个什么东西了
PS:如果你学习过 JSP 或者 Thmeleaf,我相信你对模板引擎不会陌生,关于 JSP 和 Thmeleaf 的详细介绍可以参考这两篇文章:JSP速通、Thmeleaf快速入门(Spring整合版)
什么是模板引擎?
模板引擎是一种用于生成动态内容的工具或框架。它将应用程序的逻辑和数据与视图分离,允许开发人员使用预定义的模板来定义页面、电子邮件、报表等的结构和布局。通过在模板中使用占位符、条件语句、循环等控制结构,模板引擎可以动态地将数据填充到模板中,生成最终的输出结果。这样的方法可以提高开发效率,减少代码重复,并使前后端工作更加分离。模板引擎通常支持多种语法和标记,可以集成到不同的编程语言和框架中,以满足各种应用场景的需求。
个人理解:模板引擎就是一类特殊的HTML页面,它将数据和页面骨架(HTML)、样式(CSS)与数据进行了分离,数据是需要我们动态填充(模板引擎这个概念很类似与前端的组件,模板引擎是一种后端的页面渲染技术)
模板引擎的作用是什么?
个人理解:模板引擎的主要作用是将逻辑和数据进行分离,提高复用性,相当于是给我们一个模板,我们只需要往这个模板中填充数据即可
常见的模板引擎有哪些?
模板引擎 | 说明 |
---|---|
JSP | Jsp 为 Servlet 专用,不能单独进行使用 |
Thymeleaf | 新技术,功能较为强大,但是执行的效率比较低 |
FreeMarker | 性能好,强大的模板语言、轻量 |
Velocity | Velocity从2010年更新完 2.0 版本后,7年没有更新。Spring Boot 官方在 1.4 版本后对此也不在支持 |
Freemarker的组成:
模板:模板是FreeMarker的核心组件,它定义了要生成的输出内容的结构和格式。模板使用FreeMarker的模板语法编写,可以包含变量、表达式、标签和指令等。
模板的组成:
${
and }
所分隔(或者 #{
and }
,这种风格已经不建议再使用了;点击查看更多)。<#--
和 -->
来分隔的。注释会被FreeMarker直接忽略, 更不会在输出内容中显示。模板解析器:模板解析器负责将模板文件解析为抽象语法树(AST),以便进行后续的处理和渲染。模板解析器将模板语法解析为相应的AST节点,表示模板的结构和内容。
数据模型:数据模型是FreeMarker所使用的数据源,它包含了要在模板中展示的数据。数据模型可以是任意的Java对象,例如POJO、Map、List等。在模板中可以通过表达式访问数据模型的属性和方法。
标签和指令:FreeMarker提供了一些内置的标签和指令,用于控制模板的渲染和逻辑处理。标签和指令可以包含条件判断、循环控制、变量赋值等逻辑,以及数据展示和格式化等操作。
模板渲染引擎:模板渲染引擎是FreeMarker的核心引擎,负责将模板和数据结合起来进行渲染。模板渲染引擎根据AST节点和数据模型,生成最终的输出内容。
除了上述组件之外,FreeMarker还提供了一些辅助类和工具,用于扩展和定制模板引擎的功能。例如,提供了各种自定义标签和函数的扩展点,以及模板缓存和预编译等性能优化功能。
由于平常大部分都是基于SpringBoot框架进行开发的,这里我就不基于Spring学习Freemarker了,直接基于SpringBoot演示Freemarker的使用
Step1:创建SpringBoot项目
Step2:添加依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
Step3:编写模板
在src/main/resources/templates
目录下创建 index.ftl
文件
根据 Spring Boot 约定,模板文件默认是放在
src/main/resources/templates
目录下,如果你想要自定义模板文件的位置,可以通过template-loader-path
配置属性进行设置。
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Freemarkertitle>
head>
<body>
Hello ${title}!
body>
html>
Step4:编写配置文件
spring:
freemarker:
suffix: .ftl # 模板文件的后缀名,默认为 .ftl
# content-type: text/html # 响应的文档类型,默认为 text/html
# charset: UTF-8 # 页面的编码,默认为 UTF-8
cache: false # 是否启用页面缓存,默认为 true。如果设置为 false,则每次请求都会重新解析模板文件
# template-loader-path: classpath:/templates/ # 模板文件的路径,默认为 classpath:/templates/。可以根据实际情况进行配置
注意:suffix虽然有默认值 .ftl
,但是仍然需要手动配置,否则无法成功响应模板
Step5:编写Controller
package com.ghp.freemarker.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* @author ghp
* @title
* @description
*/
@Controller
@RequestMapping("/page")
public class PageController {
@GetMapping("/index1")
public String toIndex1(){
return "index1";
}
@GetMapping("/index2")
public ModelAndView toIndex2 () {
ModelAndView modelAndView = new ModelAndView("index2");
modelAndView.addObject("title", "Freemarker");
return modelAndView;
}
}
注意:不要使用@RestController
注解表示响应模板视图的 Controller 类,因为使用@RestController
注解会将 Controller 类标识为一个 RESTful API 控制器,它主要用于返回数据或处理 API 请求,并不会返回视图页面
Step5:测试
现在可以在浏览器上分别访问 http://localhost:8080/page/index1 和 http://localhost:8080/page/index2
Freemarker是一个模板引擎,它主要用于生成动态的文本输出。它并没有特定的数据类型,而是使用 Java 对象作为模板的数据模型,所以这里的数据类型本质还是 Java 中的数据类型,本节主要讲解 Freemarker 中如何操作不同的数据类型
操作符 | 描述 |
---|---|
?c |
格式化对象 |
?string |
将对象转成字符串 |
?string.currency |
将对象转成货币类型的字符串 |
?string.percent |
将对象转成百分比类型的字符串 |
?string["0.#"] |
将数字转成字符串,并四舍五入保留一位小数 |
?time |
将日期按照“时:分:秒”输出 |
?date |
将日期按照“年-月-日”输出 |
?datetime |
将日期按照“年-月-日 时:分:秒”输出 |
?html |
对字字符串进行转移输出 |
?esc |
对字字符串进行转移输出 |
?reverse |
反转结果 |
?sort |
排序 |
?index |
获取当前遍历元素的索引 |
?sort_by |
按照某一个字段进行排序 |
这里就统一编写吧,不一个一个的CV了,太浪费时间了,直接来一个整的
/**
* 演示 Freemarker 中不同的数据类型的相关操作
*
* @return
*/
@GetMapping("/data_type")
public ModelAndView toDataType() {
// 响应的模板
ModelAndView modelAndView = new ModelAndView("data_type");
// 响应数值类型的数据
modelAndView.addObject("number", 666);
// 响应字符串类型的数据
modelAndView.addObject("string", "Freemarker");
// 响应布尔类型的数据
modelAndView.addObject("boolean", true);
// 响应日期类型的数据
modelAndView.addObject("date", new Date());
// 响应序列类型的数据(数组、List、Set)
modelAndView.addObject("sequence", Arrays.asList("张三", "李四", "王五"));
// 响应哈希类型的数据
modelAndView.addObject("hash", new HashMap<String, String>(3) {{
put("a", "A");
put("b", "B");
put("c", "C");
}});
// 还可以直接使用 addAllObjects 一次性全部添加
modelAndView.addAllObjects(new HashMap<String, Object>(6) {{
put("number2", 3.1415926);
put("string2", "Freemarker");
put("boolean2", false);
put("date2", new Date());
put("sequence2", new String[]{"张三", "李四", "王五"});
put("hash2", new HashMap<String, String>(3) {{
put("a", "A");
put("b", "B");
put("c", "C");
}});
User user1 = new User(1L, "张三", 19);
User user2 = new User(2L, "李四", 20);
User user3 = new User(3L, "李四", 21);
ArrayList<User> userList = new ArrayList<>(3);
Collections.addAll(userList, user1, user2, user3);
put("userList", userList);
Map<String, User> userMap = new HashMap<>(3);
userMap.put("key1", user1);
userMap.put("key2", user2);
userMap.put("key3", user3);
put("userMap", userMap);
}});
return modelAndView;
}
<#assign a1 = 8 a2 = 2 >
${a1} + ${a2} = ${a1 + a2} <br/>
${a1} - ${a2} = ${a1 - a2} <br/>
${a1} * ${a2} = ${a1 * a2} <br/>
${a1} / ${a2} = ${a1 / a2} <br/>
${a1} % ${a2} = ${a1 % a2} <br/>
${"hello" + "," + "freemarker"}
<#--
逻辑运算符
&&、||、!
-->
<#--
比较运算符
> (gt): 大于号,推荐使用 gt
< (lt): 小于号,推荐使用 lt
>= (gte): 大于等于, 推荐是用 gte
<= (lte): 小于等于,推荐使用 lte
== : 等于
!= : 不等于
-->
<#--
空值运算符
1. ??:判断是否为空,返回布尔类型
如果不为空返回 false, 如果为空返回 true,不能直接输出
${(name??)?string}
2. !: 设置默认值,如果为空,则设置默认值
1. 设置默认为空字符串:
${name!}
2. 设置指定默认值
${name!'zhangsan'}
-->
使用 assign 指令你可以创建一个新的变量, 或者替换一个已经存在的变量。
<#--
assign 自定义变量指令
语法:
<#assign 变量名=值>
<#assign 变量名=值 变量名=值> (定义多个变量)
-->
<#assign str="hello">
${str} <br>
<#assign num=1 names=["zhangsan","lisi","wangwu"] >
${num} -- ${names?join(",")}
可以使用 if , elseif 和 else 指令来条件判断是否满足某些条件。
<h5> 2. if, else, elseif 逻辑判断指令h5>
<#assign score = 60>
<#if score lt 60 >
<h6>你个小渣渣!h6>
<#elseif score == 80>
<h6>分不在高,及格就行!h6>
<#elseif score gt 60 && score lt 80 >
<h6>革命尚未成功,同志仍需努力!h6>
<#else >
<h6>哎哟不错哦!h6>
#if>
<#--判断数据是否存在-->
<#assign list="">
<#if list??>
数据存在
<#else >
数据不存在
#if>
<br>
<#if list2??>
数据存在
<#else >
数据不存在
#if>
可以使用 list 指令来对序列进行遍历。
<h5>3. list指令h5>
<#assign users = ["张三","李四","王五"]>
<#list users as user>
${user} |
#list>
<br>
<#--判断数据不为空,再执行遍历 (如果序列不存在,直接遍历会报错)-->
<#if users2??>
<#list users2 as user>
${user}
#list>
#if>
<br>
<#-- 当序列没有数据项时,使用默认信息 -->
<#assign users3 = []>
<#list users3 as user>
${user} |
<#else >
用户数据不存在!
#list>
可以使用 macro 指令来自定义一些自定义指令。
<#--
macro 自定义指令 (宏)
1. 基本使用
格式:
<#macro 指令名>
指令内容
#macro>
使用:
<@指令名>@指令名>
2. 有参数的自定义指令
格式:
<#macro 指令名 参数名1 参数名2>
指令内容
#macro>
使用:
<@指令名 参数名1=参数值1 参数名2=参数值2>@指令名>
注:
1. 指令可以被多次使用。
2. 自定义指令中可以包含字符串,也可包含内置指令
-->
<#-- 定义基本的自定义指令 -->
<#macro address>
© 1999–2015 The FreeMarker Project. All rights reserved.
#macro>
<#-- 使用指令 -->
<@address>@address> <br>
<@address>@address> <hr>
<#-- 定义有参数的自定义指令 -->
<#macro queryUserByName uname>
通过用户名查询用户信息 - ${uname}
#macro>
<#-- 使用指令,并传递参数 -->
<@queryUserByName uname="admin">@queryUserByName> <br>
<#-- 定义有多个参数的自定义指令 -->
<#macro queryUserByParams uname uage>
通过多个餐宿查询用户信息 - ${uname} - ${uage} #macro>
<#-- 使用指令,并传递多个参数 -->
<@queryUserByParams uname="admin" uage=18>@queryUserByParams> <br>
<hr>
<#-- 自定义指令中包含内置指令 -->
<#macro cfb>
<#list 1..9 as i>
<#list 1..i as j>
${j}*${i}=${j*i}
#list>
<br>
#list>
#macro>
<@cfb>@cfb>
<@cfb>@cfb>
<#-- 动态数据 -->
<#macro cfb2 num>
<#list 1..num as i>
<#list 1..i as j>
${j}*${i}=${j*i}
#list>
<br>
#list>
#macro>
<@cfb2 num=5>@cfb2>
nested 指令执行自定义指令开始和结束标签中间的模板片段。嵌套的片段可以包含模板中任意合法的内容。
<#--
nested 占位指令
nested 相当于占位符,一般结合macro指令一起使用。
可以将自定义指令中的内容通过nested指令占位,当使用自定义指令时,会将占位内容显示。
-->
<#macro test>
这是一段文本!
<#nested>
<#nested>
#macro>
<@test><h4>这是文本后面的内容!h4>@test>
import 指令可以引入一个库。也就是说,它创建一个新的命名空间, 然后在那个命名空间中执行给定路径的模
板。可以使用引入的空间中的指令。
1)创建commons.ftl文件
<#macro cfb>
<#list 1..9 as i>
<#list 1..i as j>
${j}*${i}=${j*i}
#list>
<br>
#list>
#macro>
在其他ftl页面中通过import导入commons.ftl的命名空间,使用该命名空间中的指令
2)创建test.ftl文件
<#-- 导入命名空间 -->
<#import "commons.ftl" as common>
<#-- 使用命名空间中的指令 -->
<@common.cfb>@common.cfb>
3)创建Controller
package org.example.controller;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author ghp
* @description Freemarker常用指令
* @date 2022/12/20 14:05
*/
@WebServlet("/f04")
public class FreeMarker04 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 请求转发到指定的页面,
req.getRequestDispatcher("template/test.ftl").forward(req, resp);
}
}
可以使用 include 指令在你的模板中插入另外一个 FreeMarker 模板文件 。 被包含模板的输出格式是在 include
标签出现的位置插入的。 被包含的文件和包含它的模板共享变量,就像是被复制粘贴进去的一样。
<#--包含指令(引入其他页面文件) include-->
<#--html文件-->
<#include "test.html">
<#--freemarker文件-->
<#include "test.ftl">
<#--text文件-->
<#include "test.txt">
参考资料:
- 什么是 FreeMarker? - FreeMarker 中文官方参考手册 (foofun.cn)
- Freemarker笔记.md · liyonghui/Freemarker_readme - Gitee.com
- Spring Boot 整合 Freemarker 模板引擎 - spring 中文网 (springdoc.cn)