SpringBoot結(jié)合FreeMarker視圖渲染的實現(xiàn)
一、FreeMarker 簡介
1.1 什么是FreeMarker?
FreeMarker是一款 模板引擎 ,它允許開發(fā)人員使用模板和數(shù)據(jù)來生成輸出文本,如HTML網(wǎng)頁、電子郵件、配置文件和源代碼等。它是一個Java類庫,可以被嵌入到開發(fā)人員所創(chuàng)建的產(chǎn)品中。開發(fā)人員可以使用FreeMarker來動態(tài)生成和渲染視圖,以便將數(shù)據(jù)呈現(xiàn)給最終用戶。
https://freemarker.apache.org/
下面是一些關(guān)鍵點:
模板引擎: FreeMarker是一種模板引擎,允許開發(fā)者定義模板文件,其中包含要動態(tài)填充的占位符或表達式。這些模板文件可以包含HTML、XML、文本等。
動態(tài)內(nèi)容生成: FreeMarker的主要用途是根據(jù)模板和數(shù)據(jù)生成最終的輸出內(nèi)容。模板中的占位符將被實際的數(shù)據(jù)替代,從而生成動態(tài)的、個性化的輸出。
嵌入式組件: FreeMarker是一個Java類庫,可以輕松地集成到Java應(yīng)用程序中。這使得開發(fā)人員可以在他們的Java應(yīng)用中使用FreeMarker來處理視圖層的動態(tài)內(nèi)容生成。
面向程序員: FreeMarker通常被程序員用于構(gòu)建動態(tài)的用戶界面或生成其他類型的文本輸出。它不直接面向最終用戶,而是提供給開發(fā)人員一個工具,使他們能夠以更靈活和動態(tài)的方式生成內(nèi)容。
1.2 Freemarker模板組成部分
FreeMarker模板文件主要由如下4個部分組成:
文本:直接輸出的部分
注釋:使用 <#-- ... --> 格式做注釋,里面內(nèi)容不會輸出
插值:即 ${...} 或 #{...} 格式的部分,類似于占位符,將使用數(shù)據(jù)模型中的部分替代輸出
FTL指令:即FreeMarker指令,全稱是:FreeMarker Template Language,和HTML標(biāo)記類似,但名字前加#予以區(qū)分,不會輸出
1.3 為什么要使用FreeMarker
FreeMarker在Spring Boot中被廣泛使用的原因與其特性和與Spring Boot的集成有關(guān)。
輕量級: FreeMarker相對輕量,不引入過多的依賴,易于集成和使用。
與Spring框架整合良好: FreeMarker與Spring框架集成良好,通過Spring Boot的自動配置,可以很容易地配置FreeMarker作為模板引擎。
Spring Boot 默認支持: Spring Boot對多個模板引擎提供了自動配置支持,包括FreeMarker。因此,Spring Boot項目中使用FreeMarker非常方便,只需在依賴中引入相應(yīng)的starter即可。
開箱即用: FreeMarker可以作為Spring Boot的一部分,無需額外的配置。這使得開發(fā)者能夠快速啟動項目并使用FreeMarker構(gòu)建視圖。
簡化模板文件的構(gòu)建: FreeMarker的模板語法相對簡潔,可以更容易地與后端Java代碼交互。在Spring Boot項目中,Java對象的數(shù)據(jù)可以直接在FreeMarker模板中使用。
FreeMarker的誕生是為了取代JSP。雖然JSP功能強大,可以寫Java代碼實現(xiàn)復(fù)雜的邏輯處理,但是頁面會有大量業(yè)務(wù)邏輯,不利于維護和閱讀,更不利于前后臺分工,容易破壞MVC結(jié)構(gòu),所以舍棄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: # 設(shè)置模板后綴名 suffix: .ftl # 設(shè)置文檔類型 content-type: text/html # 設(shè)置頁面編碼格式 charset: UTF-8 # 設(shè)置頁面緩存 cache: false # 設(shè)置ftl文件路徑 template-loader-path: classpath:/templates # 設(shè)置靜態(tài)文件路徑,js,css等 mvc: static-path-pattern: /static/**
3、新建模板文件(.ftl)
在resources/templates目錄新建一個.ftl文件
第一創(chuàng)建是沒有這個文件類型的選項的,所以我們要定義個.ftl格式的文件:
settings --> Editor --> File and Code Templates
該內(nèi)容模板于html差不多的,最后在創(chuàng)建文件的時候就有個一個.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"; } }
一個基本的數(shù)據(jù)顯示就成功了
2.2 數(shù)據(jù)類型
2.2.1 字符串
在文本中確定字符串值的方法是看雙引號,比如: "some text"
,或單引號,比如: 'some text'
。這兩種形式是等同的。 如果文本自身包含用于字符引用的引號 ( "
或 '
)或反斜杠時, 應(yīng)該在它們的前面再加一個反斜杠;這就是轉(zhuǎn)義。 轉(zhuǎn)義允許直接在文本中輸入任何字符, 也包括換行。
${"It's \"quoted\" and this is a backslash: \\"}
字符串類型處理:
方法 | 含義 |
---|---|
?substring(start,end) | 截取字符串(左閉右開) |
?uncap_first | 首字母小寫輸出 |
?cap_first | 首字母大寫輸出 |
?lower_case | 字母轉(zhuǎn)小寫輸出 |
?upper_case | 字母轉(zhuǎn)大寫輸出 |
?length | 獲取字符串長度 |
?starts_with("xx")?string | 是否以指定字符開頭(boolean類型) |
?ends_with("xx")?string | 是否以指定字符結(jié)尾(boolean類型) |
?index_of("xx") | 獲取指定字符的索引 |
?trim | 去除字符串前后空格 |
?replace("xx","xx") | 替換指定字符串 |
字符串空值情況處理:
FreeMarker 的變量必須賦值,否則就會拋出異常。而對于 FreeMarker 來說,null 值和不存在的變量是完全一樣的,因為 FreeMarker 無法理解 null 值。
<#-- 如果值不存在,直接輸出會報錯 --> <#--${str}--> <#-- 使用!,當(dāng)值不存在時,默認顯示空字符串 --> ${str!} <#-- 使用!"xx",當(dāng)值不存在時,默認顯示指定字符串 --> ${str!"這是一個默認值"} <#-- 使用??,判斷字符串是否為空;返回布爾類型。如果想要輸出,需要將布爾類型轉(zhuǎn)換成字符串 --> ${(str??)?string}
注意事項:
使用??或?starts_with("xx"),判斷字符串是否為空或指定字符開頭;返回布爾類型。如果想要輸出,需要將布爾類型轉(zhuǎn)換成字符串在后面添加 ?c or ?string
例如:
${"嗨害嗨"?starts_with("我")} ${str??}
不進行轉(zhuǎn)換則會報錯
2.2.2 數(shù)值
輸入不帶引號的數(shù)字就可以直接指定一個數(shù)字, 必須使用點作為小數(shù)的分隔符而不能是其他的分組分隔符。
${0.45} ${18} <#-- 將數(shù)值轉(zhuǎn)換成字符串輸出 --> ${1000?c} <#-- 將數(shù)值轉(zhuǎn)換成貨幣類型的字符串輸出 --> ${1000?string.currency} <#-- 將數(shù)值轉(zhuǎn)換成百分比類型的字符串輸出 --> ${0.45?string.percent} <#-- 將浮點型數(shù)值保留指定小數(shù)位輸出 (##表示保留兩位小數(shù)) --> ${0.45723123?string["0.##"]}
2.2.3 布爾值
直接寫 true
或者 false
就表示一個布爾值了,不需使用引號。
在freemarker中布爾類型不能直接輸出;如果輸出要先轉(zhuǎn)成字符串
${flag?c} ${flag?string} ${flag?string("yes","no")}
2.2.4 日期
日期變量可以存儲和日期/時間相關(guān)的數(shù)據(jù)。
在freemarker中日期類型不能直接輸出;如果輸出要先轉(zhuǎn)成日期型或字符串
日期格式輸出:
輸出方式 | 說明 |
---|---|
?date | 年月日 |
?time | 時分秒 |
?datetime | 年月日時分秒 |
?string("自定義格式") | 指定格式 |
<#-- 輸出日期格式 --> ${createDate?date} <#-- 輸出時間格式 --> ${createDate?time} <#-- 輸出日期時間格式 --> ${createDate?datetime} <#-- 輸出格式化日期格式 --> ${createDate?string("yyyy年MM月dd日 HH時mm分ss秒")}
注意事項:
在.ftl文件中不能直接創(chuàng)建時間需要通過后端傳入時間進行顯示(替換掉createDate)
2.3 常見指令
2.3.2 assign
使用該指令你可以創(chuàng)建一個新的變量, 或者替換一個已經(jīng)存在的變量。
案例演示:
<#-- 創(chuàng)建一個str的變量 --> <#assign str="hello"> <#-- 輸出str --> ${str} <#-- 一次創(chuàng)建多個變量 --> <#assign num=1 names=["嗨","害","嗨"] > ${num} and ${names?join(",")}
2.3.3 include
可以使用它在你的模板中插入另外一個 FreeMarker 模板文件 (由 path
參數(shù)指定)
該標(biāo)簽主要用于引入其他資源,簡化數(shù)據(jù)
首先創(chuàng)建一個公共頁面定義一個變量存儲本地訪問路徑
在index.ftl中進行引入調(diào)用src數(shù)值
<#-- 引入公共頁面 --> <#include "common.ftl"> <a href="${src}/xxx" rel="external nofollow" ></a>
...
還有如:if/elseif/else、list指令在下面的綜合案例中進行演示??
三、常見指令實現(xiàn)增刪改查(綜合案例)?
3.1 后端
BookMapper.xml
<select id="list" resultMap="BaseResultMap" resultType="com.ycxw.boot.entity.Book"> select <include refid="Base_Column_List"/> from t_book where 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(書本信息表)】的數(shù)據(jù)庫操作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 { @Autowired private BookMapper bookMapper; @Override public List<Book> list(String bookname) { return bookMapper.list(bookname); } @Override public int insert(Book record) { return bookMapper.insert(record); } @Override public int deleteByPrimaryKey(Long id) { return bookMapper.deleteByPrimaryKey(id); } @Override public 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 { @Autowired private 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 進行相關(guān)的處理 List<Book> list = bookService.list(bookname); model.addAttribute("book", list); return "index"; // 返回搜索結(jié)果頁面 } }
3.2 前端
common.ftl (公共資源)
<#--引入當(dāng)前項目的運行路徑--> <#assign src="${springMacroRequestContext.contextPath}"> <#--引入樣式與腳本--> <link rel="stylesheet" rel="external nofollow" > <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}" rel="external nofollow" >刪除</a> <a href="javascript:void(0);" rel="external nofollow" 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="請輸入標(biāo)題"> </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="文學(xué)">文學(xué)</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="請輸入標(biāo)題"> </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="文學(xué)">文學(xué)</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(); // 發(fā)送AJAX請求 $.ajax({ url: "${src}/search", type: "POST", contentType: "application/x-www-form-urlencoded", data: {"bookname": name}, success: function (data) { //更新整個頁面元素 $("#body").html(data); } }); } /*新增方法*/ function add() { // 獲取表單數(shù)據(jù) var name = $("#name").val(); var type = $("#bookType").val(); var price = $("#price").val(); // 創(chuàng)建一個對象來存儲表單數(shù)據(jù) var book = { bookname: name, booktype: type, price: price }; // 發(fā)送AJAX請求 $.ajax({ url: "${src}/add", type: "POST", contentType: "application/json", data: JSON.stringify(book), success: function (res) { console.log(res); // 請求成功處理邏輯 alert("書籍添加成功!"); // 關(guān)閉彈窗 var modal = $("#exampleModal"); modal.modal("hide"); // 刷新頁面 location.reload(); } }); } /* 修改方法(回顯數(shù)據(jù)) */ function editModal(id, name, type, price) { // 將數(shù)據(jù)設(shè)置到彈窗表單中 $("#id").val(id); $("#name_edit").val(name); $("#type_edit").val(type); $("#price_edit").val(price); // 顯示修改彈窗 $("#editModal").modal("show"); } /*修改方法*/ function edit() { // 獲取表單數(shù)據(jù) var id = $("#id").val(); var name = $("#name_edit").val(); var type = $("#type_edit").val(); var price = $("#price_edit").val(); // 創(chuàng)建一個對象來存儲表單數(shù)據(jù) var book = { id: id, bookname: name, booktype: type, price: price }; // 發(fā)送AJAX請求 $.ajax({ url: "${src}/edit", type: "PUT", contentType: "application/json", data: JSON.stringify(book), success: function (res) { console.log(res); // 請求成功處理邏輯 alert("書籍修改成功!"); // 關(guān)閉彈窗 var modal = $("#editModal"); modal.modal("hide"); // 刷新頁面 location.reload(); } }); } </script> </html>
3.3 效果展示
3.3.1 新增功能
3.3.2 修改功能
3.3.3 查詢功能
3.3.4 刪除功能
到此這篇關(guān)于SpringBoot結(jié)合FreeMarker視圖渲染的實現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot FreeMarker視圖渲染內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
RocketMQ producer同步發(fā)送單向發(fā)送源碼解析
這篇文章主要為大家介紹了RocketMQ producer同步發(fā)送單向發(fā)送源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03Java學(xué)習(xí)-打印1-1000以內(nèi)的水仙花數(shù)代碼實例
這篇文章主要介紹了Java打印1-1000以內(nèi)的水仙花數(shù),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Java 類型相互轉(zhuǎn)換byte[]類型,Blob類型詳細介紹
這篇文章主要介紹了Java 類型相互轉(zhuǎn)換byte[]類型,Blob類型的相關(guān)資料,需要的朋友可以參考下2016-10-10Java中的List和MySQL中的varchar相互轉(zhuǎn)換的解決方案
實體類中有一個 List<String> 類型的屬性,對應(yīng)于 MySQL 表里的 varchar 字段,使用 MyBatis 添加或查詢時能互相轉(zhuǎn)換,本文給大家介紹Java中的List和MySQL中的varchar相互轉(zhuǎn)換的解決方案,需要的朋友可以參考下2024-06-06springboot+jersey+tomcat實現(xiàn)跨域方式上傳文件到服務(wù)器的方式
這篇文章主要介紹了springboot+jersey+tomcat實現(xiàn)跨域方式上傳文件到服務(wù)器,本文結(jié)合實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03關(guān)于Java?中?Future?的?get?方法超時問題
這篇文章主要介紹了Java?中?Future?的?get?方法超時,最常見的理解就是,“超時以后,當(dāng)前線程繼續(xù)執(zhí)行,線程池里的對應(yīng)線程中斷”,真的是這樣嗎?本文給大家詳細介紹,需要的朋友參考下吧2022-06-06Java?Stream實現(xiàn)多字段分組groupingBy操作詳解
Stream是Java8的一個新特性,主要用戶集合數(shù)據(jù)的處理,如排序、過濾、去重等等功能,本文就來講講如何利用Stream實現(xiàn)比較優(yōu)雅的按多字段進行分組groupingBy吧2023-06-06