目录
一、FreeMarker 简介
1.1 什么是FreeMarker?
1.2 Freemarker模板组成部分
1.3 为什么要使用FreeMarker
二、Springboot集成FreeMarker
2.1 配置
2.2 数据类型
2.2.1 字符串
2.2.2 数值
2.2.3 布尔值
2.2.4 日期
2.3 常见指令
2.3.2 assign
2.3.3 include
...
三、常见指令实现增删改查(综合案例)⭐
3.1 后端
3.2 前端
3.3 效果展示
3.3.1 新增功能
3.3.2 修改功能
3.3.3 查询功能
3.3.4 删除功能
一、FreeMarker 简介
1.1 什么是FreeMarker?
FreeMarker是一款 模板引擎 ,它允许开发人员使用模板和数据来生成输出文本,如HTML网页、电子邮件、配置文件和源代码等。它是一个Java类库,可以被嵌入到开发人员所创建的产品中。开发人员可以使用FreeMarker来动态生成和渲染视图,以便将数据呈现给最终用户。
官方手册https://freemarker.apache.org/
下面是一些关键点:
-
模板引擎: FreeMarker是一种模板引擎,允许开发者定义模板文件,其中包含要动态填充的占位符或表达式。这些模板文件可以包含HTML、XML、文本等。
-
动态内容生成: FreeMarker的主要用途是根据模板和数据生成最终的输出内容。模板中的占位符将被实际的数据替代,从而生成动态的、个性化的输出。
-
嵌入式组件: FreeMarker是一个Java类库,可以轻松地集成到Java应用程序中。这使得开发人员可以在他们的Java应用中使用FreeMarker来处理视图层的动态内容生成。
-
面向程序员: FreeMarker通常被程序员用于构建动态的用户界面或生成其他类型的文本输出。它不直接面向最终用户,而是提供给开发人员一个工具,使他们能够以更灵活和动态的方式生成内容。
1.2 Freemarker模板组成部分
FreeMarker模板文件主要由如下4个部分组成:
-
文本:直接输出的部分
-
注释:使用 <#-- ... --> 格式做注释,里面内容不会输出
-
插值:即 ${...} 或 #{...} 格式的部分,类似于占位符,将使用数据模型中的部分替代输出
-
FTL指令:即FreeMarker指令,全称是:FreeMarker Template Language,和HTML标记类似,但名字前加#予以区分,不会输出
1.3 为什么要使用FreeMarker
FreeMarker在Spring Boot中被广泛使用的原因与其特性和与Spring Boot的集成有关。
-
轻量级: FreeMarker相对轻量,不引入过多的依赖,易于集成和使用。
-
与Spring框架整合良好: FreeMarker与Spring框架集成良好,通过Spring Boot的自动配置,可以很容易地配置FreeMarker作为模板引擎。
-
Spring Boot 默认支持: Spring Boot对多个模板引擎提供了自动配置支持,包括FreeMarker。因此,Spring Boot项目中使用FreeMarker非常方便,只需在依赖中引入相应的starter即可。
-
开箱即用: FreeMarker可以作为Spring Boot的一部分,无需额外的配置。这使得开发者能够快速启动项目并使用FreeMarker构建视图。
-
简化模板文件的构建: FreeMarker的模板语法相对简洁,可以更容易地与后端Java代码交互。在Spring Boot项目中,Java对象的数据可以直接在FreeMarker模板中使用。
FreeMarker的诞生是为了取代JSP。虽然JSP功能强大,可以写Java代码实现复杂的逻辑处理,但是页面会有大量业务逻辑,不利于维护和阅读,更不利于前后台分工,容易破坏MVC结构,所以舍弃JSP,选择使用FreeMarker是大势所趋。
二、Springboot集成FreeMarker
2.1 配置
1、配置pom.xml,引入依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
2、添加application.yml配置:
freemarker:# 设置模板后缀名suffix: .ftl# 设置文档类型content-type: text/html# 设置页面编码格式charset: UTF-8# 设置页面缓存cache: false# 设置ftl文件路径template-loader-path: classpath:/templates# 设置静态文件路径,js,css等mvc:static-path-pattern: /static/**
3、新建模板文件(.ftl)
在resources/templates目录新建一个.ftl文件
第一创建是没有这个文件类型的选项的,所以我们要定义个.ftl格式的文件:
settings --> Editor --> File and Code Templates
该内容模板于html差不多的,最后在创建文件的时候就有个一个.ftl格式的文件选项
index.ftl:
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>Freemarker</title>
</head>
<body>
<h1>Hello FreeMarker!!!</h1>
</body>
</html>
Controller:
package com.ycxw.boot.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;/*** @author 云村小威* @create 2023-12-13 18:57*/
@Controller
public class IndexController {@RequestMapping("/")public String home(){return "index";}
}
一个基本的数据显示就成功了
2.2 数据类型
2.2.1 字符串
在文本中确定字符串值的方法是看双引号,比如: "some text"
,或单引号,比如: 'some text'
。这两种形式是等同的。 如果文本自身包含用于字符引用的引号 ( "
或 '
)或反斜杠时, 应该在它们的前面再加一个反斜杠;这就是转义。 转义允许直接在文本中输入任何字符, 也包括换行。
${"It's \"quoted\" and this is a backslash: \\"}
字符串类型处理:
方法 | 含义 |
---|---|
?substring(start,end) | 截取字符串(左闭右开) |
?uncap_first | 首字母小写输出 |
?cap_first | 首字母大写输出 |
?lower_case | 字母转小写输出 |
?upper_case | 字母转大写输出 |
?length | 获取字符串长度 |
?starts_with("xx")?string | 是否以指定字符开头(boolean类型) |
?ends_with("xx")?string | 是否以指定字符结尾(boolean类型) |
?index_of("xx") | 获取指定字符的索引 |
?trim | 去除字符串前后空格 |
?replace("xx","xx") | 替换指定字符串 |
字符串空值情况处理:
FreeMarker 的变量必须赋值,否则就会抛出异常。而对于 FreeMarker 来说,null 值和不存在的变量是完全一样的,因为 FreeMarker 无法理解 null 值。
<#-- 如果值不存在,直接输出会报错 -->
<#--${str}-->
<#-- 使用!,当值不存在时,默认显示空字符串 -->
${str!}<br>
<#-- 使用!"xx",当值不存在时,默认显示指定字符串 -->
${str!"这是一个默认值"}<br>
<#-- 使用??,判断字符串是否为空;返回布尔类型。如果想要输出,需要将布尔类型转换成字符串 -->
${(str??)?string}<br>
注意事项:
使用??或?starts_with("xx"),判断字符串是否为空或指定字符开头;返回布尔类型。如果想要输出,需要将布尔类型转换成字符串在后面添加 ?c or ?string
例如:
${"嗨害嗨"?starts_with("我")} ${str??}不进行转换则会报错
2.2.2 数值
输入不带引号的数字就可以直接指定一个数字, 必须使用点作为小数的分隔符而不能是其他的分组分隔符。
${0.45}<br>
${18}<br>
<#-- 将数值转换成字符串输出 -->
${1000?c} <br>
<#-- 将数值转换成货币类型的字符串输出 -->
${1000?string.currency} <br>
<#-- 将数值转换成百分比类型的字符串输出 -->
${0.45?string.percent} <br>
<#-- 将浮点型数值保留指定小数位输出 (##表示保留两位小数) -->
${0.45723123?string["0.##"]} <br>
2.2.3 布尔值
直接写 true
或者 false
就表示一个布尔值了,不需使用引号。
在freemarker中布尔类型不能直接输出;如果输出要先转成字符串
${flag?c}<br>
${flag?string}<br>
${flag?string("yes","no")}<br>
2.2.4 日期
日期变量可以存储和日期/时间相关的数据。
在freemarker中日期类型不能直接输出;如果输出要先转成日期型或字符串
日期格式输出:
输出方式 | 说明 |
---|---|
?date | 年月日 |
?time | 时分秒 |
?datetime | 年月日时分秒 |
?string("自定义格式") | 指定格式 |
<#-- 输出日期格式 -->
${createDate?date} <br>
<#-- 输出时间格式 -->
${createDate?time} <br>
<#-- 输出日期时间格式 -->
${createDate?datetime} <br>
<#-- 输出格式化日期格式 -->
${createDate?string("yyyy年MM月dd日 HH时mm分ss秒")} <br>
注意事项:
在.ftl文件中不能直接创建时间需要通过后端传入时间进行显示(替换掉createDate)
2.3 常见指令
2.3.2 assign
使用该指令你可以创建一个新的变量, 或者替换一个已经存在的变量。
案例演示:
<#-- 创建一个str的变量 -->
<#assign str="hello">
<#-- 输出str -->
${str} <br>
<#-- 一次创建多个变量 -->
<#assign num=1 names=["嗨","害","嗨"] >
${num} and ${names?join(",")}
2.3.3 include
可以使用它在你的模板中插入另外一个 FreeMarker 模板文件 (由 path
参数指定)
该标签主要用于引入其他资源,简化数据
首先创建一个公共页面定义一个变量存储本地访问路径
在index.ftl中进行引入调用src数值
<#-- 引入公共页面 -->
<#include "common.ftl">
<a href="${src}/xxx"></a>
...
还有如:if/elseif/else、list指令在下面的综合案例中进行演示👇
三、常见指令实现增删改查(综合案例)⭐
3.1 后端
BookMapper.xml
<select id="list" resultMap="BaseResultMap" resultType="com.ycxw.boot.entity.Book">select<include refid="Base_Column_List"/>from t_bookwhere 1=1<if test="bookname !=null and bookname !=''">and bookname like CONCAT('%',#{bookname},'%')</if></select>
在此添加了模糊查询的方法,其他的增删改都是自动生成。
BookMapper.java
package com.ycxw.boot.mapper;import com.ycxw.boot.entity.Book;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;import java.util.List;/**
* @author 云村小威
* @description 针对表【t_book(书本信息表)】的数据库操作Mapper
* @createDate 2023-12-12 14:50:45
* @Entity com.ycxw.boot.entity.Book
*/
@Repository
public interface BookMapper {List<Book> list(@Param("bookname")String bookname);...其他方法
}
BookService.java
package com.ycxw.boot.service;import com.ycxw.boot.entity.Book;
import org.apache.ibatis.annotations.Param;import java.util.List;/*** @author 云村小威* @create 2023-12-12 15:11*/
public interface BookService {List<Book> list(@Param("bookname")String bookname);int insert(Book record);int deleteByPrimaryKey(Long id);int updateByPrimaryKey(Book record);
}
BookServiceImpl.java
package com.ycxw.boot.service.impl;import com.ycxw.boot.entity.Book;
import com.ycxw.boot.mapper.BookMapper;
import com.ycxw.boot.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;/*** @author 云村小威* @create 2023-12-12 15:12*/
@Service
public class BookServiceImpl implements BookService {@Autowiredprivate BookMapper bookMapper;@Overridepublic List<Book> list(String bookname) {return bookMapper.list(bookname);}@Overridepublic int insert(Book record) {return bookMapper.insert(record);}@Overridepublic int deleteByPrimaryKey(Long id) {return bookMapper.deleteByPrimaryKey(id);}@Overridepublic int updateByPrimaryKey(Book record) {return bookMapper.updateByPrimaryKey(record);}
}
IndexController.java
package com.ycxw.boot.controller;import com.ycxw.boot.entity.Book;
import com.ycxw.boot.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;import java.util.List;/*** @author 云村小威* @create 2023-12-13 18:57*/
@Controller
public class IndexController {@Autowiredprivate BookService bookService;/*** 查询所有** @param model* @return*/@RequestMapping("/")public String home(Model model) {List<Book> list = bookService.list(null);model.addAttribute("book", list);return "index";}/*** 新增** @param book* @return*/@PostMapping("/add")public String add(@RequestBody Book book) {bookService.insert(book);return "redirect:/";}/*** 删除** @param book* @return*/@RequestMapping("/del")public String del(Book book) {bookService.deleteByPrimaryKey(book.getId().longValue());return "redirect:/";}/*** 修改** @param book* @return*/@PutMapping("/edit")public String edit(@RequestBody Book book) {bookService.updateByPrimaryKey(book);return "redirect:/";}/*** 模糊查询** @param bookname* @param model* @return*/@RequestMapping(value = "/search", method = RequestMethod.POST)public String search(@RequestParam("bookname") String bookname, Model model) {// 在这里使用 bookname 进行相关的处理List<Book> list = bookService.list(bookname);model.addAttribute("book", list);return "index"; // 返回搜索结果页面}}
3.2 前端
common.ftl (公共资源)
<#--引入当前项目的运行路径-->
<#assign src="${springMacroRequestContext.contextPath}">
<#--引入样式与脚本-->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
<!-- 引入Bootstrap的JavaScript文件(需要在jQuery之后引入) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js"></script>
index.ftl(自定义引擎模版)
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>FreeMarker</title><#--引入公共模块--><#include "common.ftl">
</head>
<body>
<div class="container mt-5" id="body"><div class="row justify-content-center"><div class="col-3"><input type="text" class="form-control" id="input" placeholder="请输入书籍名称"style="width: 300px"/></div><div class="col-2"><button type="submit" class="btn btn-dark mb-3" onclick="query()">搜索</button><button type="button" class="btn btn-outline-dark mb-3" data-bs-toggle="modal"data-bs-target="#exampleModal">新增书籍</button></div></div><div class="row"><table class="table table-hover" style="width: 65%;margin: auto"><thead class="table-dark"><tr><th>序号</th><th>书籍名称</th><th>价格</th><th>类型</th><th>操作</th></tr></thead><tbody><#if book??><#list book as b><tr><td>${b.id}</td><td>${b.bookname}</td><td>${b.price}</td><td>${b.booktype}</td><td><a href="${src}/del?id=${b.id}">删除</a><a href="javascript:void(0);"onclick="editModal('${b.id}', '${b.bookname}', '${b.booktype}', ${b.price})"data-bs-toggle="modal" data-bs-target="#editModal">修改</a></td></tr></#list></#if></tbody></table></div>
</div>
<!-- 新增弹窗 -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title" id="exampleModalLabel">新增书籍</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><!-- 表单 --><form><div class="mb-3"><label for="title" class="form-label">书籍名称</label><input type="text" class="form-control" id="name" placeholder="请输入标题"></div><div class="mb-3"><label for="author" class="form-label">类型</label><select class="form-select" id="bookType" aria-label="Default select example"><option selected>请选择书籍类型</option><option value="动作">动作</option><option value="冒险">冒险</option><option value="文学">文学</option></select></div><div class="mb-3"><label for="price" class="form-label">价格</label><input type="number" class="form-control" id="price" placeholder="请输入价格"></div><button type="button" class="btn btn-outline-dark" data-bs-dismiss="modal" aria-label="Close">取消</button><button type="button" class="btn btn-dark" onclick="add()">提交</button></form></div></div></div>
</div>
<!-- 修改弹窗 -->
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title" id="exampleModalLabel">修改书籍</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><!-- 表单 --><form><div class="mb-3"><label for="id" class="form-label">书籍编号</label><input type="text" class="form-control" id="id" disabled></div><div class="mb-3"><label for="title" class="form-label">书籍名称</label><input type="text" class="form-control" id="name_edit" placeholder="请输入标题"></div><div class="mb-3"><label for="author" class="form-label">类型</label><select class="form-select" id="type_edit" aria-label="Default select example"><option selected>请选择书籍类型</option><option value="动作">动作</option><option value="冒险">冒险</option><option value="文学">文学</option></select></div><div class="mb-3"><label for="price" class="form-label">价格</label><input type="number" class="form-control" id="price_edit" placeholder="请输入价格"></div><button type="button" class="btn btn-outline-dark" data-bs-dismiss="modal" aria-label="Close">取消</button><button type="button" class="btn btn-dark" onclick="edit()">确认修改</button></form></div></div></div>
</div>
</body>
<script>/*模糊查询*/function query() {//获取输入值var name = $("#input").val();// 发送AJAX请求$.ajax({url: "${src}/search",type: "POST",contentType: "application/x-www-form-urlencoded",data: {"bookname": name},success: function (data) {//更新整个页面元素$("#body").html(data);}});}/*新增方法*/function add() {// 获取表单数据var name = $("#name").val();var type = $("#bookType").val();var price = $("#price").val();// 创建一个对象来存储表单数据var book = {bookname: name,booktype: type,price: price};// 发送AJAX请求$.ajax({url: "${src}/add",type: "POST",contentType: "application/json",data: JSON.stringify(book),success: function (res) {console.log(res);// 请求成功处理逻辑alert("书籍添加成功!");// 关闭弹窗var modal = $("#exampleModal");modal.modal("hide");// 刷新页面location.reload();}});}/* 修改方法(回显数据) */function editModal(id, name, type, price) {// 将数据设置到弹窗表单中$("#id").val(id);$("#name_edit").val(name);$("#type_edit").val(type);$("#price_edit").val(price);// 显示修改弹窗$("#editModal").modal("show");}/*修改方法*/function edit() {// 获取表单数据var id = $("#id").val();var name = $("#name_edit").val();var type = $("#type_edit").val();var price = $("#price_edit").val();// 创建一个对象来存储表单数据var book = {id: id,bookname: name,booktype: type,price: price};// 发送AJAX请求$.ajax({url: "${src}/edit",type: "PUT",contentType: "application/json",data: JSON.stringify(book),success: function (res) {console.log(res);// 请求成功处理逻辑alert("书籍修改成功!");// 关闭弹窗var modal = $("#editModal");modal.modal("hide");// 刷新页面location.reload();}});}
</script>
</html>