Spring Boot 使用模板引擎開(kāi)發(fā) Web 項(xiàng)目
1. 前言
模板引擎這個(gè)詞,咋聽(tīng)起來(lái),有點(diǎn)高大上的意味。
實(shí)際上,模板引擎是非常平易近人的技術(shù)。譬如大家可能都比較熟悉的 JSP ,就是一種比較典型的模板引擎。
當(dāng)瀏覽器將請(qǐng)求拋給控制器,控制器處理好數(shù)據(jù)后,就跳轉(zhuǎn) JSP 等模板引擎頁(yè)面。注意在跳轉(zhuǎn)的同時(shí),還會(huì)將數(shù)據(jù)組裝好,也交給模板引擎處理。
模板引擎會(huì)根據(jù)數(shù)據(jù),和模板引擎的規(guī)則,動(dòng)態(tài)生成 HTML 頁(yè)面,最后返回給瀏覽器顯示。
2. 模板引擎使用場(chǎng)景
我們使用 Spring Boot 開(kāi)發(fā) Web 項(xiàng)目,大體上有兩種方式。
第一種方式,是后端服務(wù)化的方式,也是當(dāng)前的主流方式。前端是靜態(tài)的 HTML 頁(yè)面,通過(guò) Ajax 請(qǐng)求 Spring Boot 的后端接口。 Spring Boot 返回?cái)?shù)據(jù)一般采用 JSON 格式,前端接收后將數(shù)據(jù)顯示。
第二種方式,是采取模板引擎的方式。前端的請(qǐng)求,到達(dá) Spring Boot 的控制器后,控制器處理請(qǐng)求,然后將返回?cái)?shù)據(jù)交給模板引擎。模板引擎負(fù)責(zé)根據(jù)數(shù)據(jù)生成 HTML 頁(yè)面,最后將 HTML 返回給瀏覽器。
我個(gè)人比較推薦第一種方式,說(shuō)一下該方式的幾個(gè)優(yōu)點(diǎn):
- 便于分工協(xié)作:后端可以按自己的進(jìn)度開(kāi)發(fā)接口,前端可以開(kāi)發(fā)頁(yè)面,需要的時(shí)候直接調(diào)用后端 API ;
- 便于項(xiàng)目拓展:比如前期是做的網(wǎng)站,后續(xù)要加一個(gè) APP ,后端接口可以直接復(fù)用;
- 降低服務(wù)端壓力:后端只提供數(shù)據(jù),一部分業(yè)務(wù)邏輯在前端處理了。服務(wù)端要做的事情少了,自然壓力就小。
本篇是講模板引擎,也得說(shuō)說(shuō)模板引擎的優(yōu)點(diǎn),王婆賣(mài)瓜不能光夸草莓啊。模板引擎開(kāi)發(fā)的頁(yè)面,對(duì)搜索引擎 SEO 比較友好;還有就是簡(jiǎn)單的頁(yè)面,如果用模板引擎開(kāi)發(fā)速度比較快,畢竟模板化的方法,目的就是減少重復(fù)提高效率。
3. Spring Boot 中常用的模板引擎
Spring Boot 支持的模板引擎種類(lèi)很多,常見(jiàn)的有 FreeMarker 、 Thymeleaf 、 JSP 。
因?yàn)檫@些模板引擎使用的用戶(hù)都不少,所以我們逐一介紹下其實(shí)現(xiàn)過(guò)程。
至于孰優(yōu)孰劣,請(qǐng)各位看官自行評(píng)價(jià)。正所謂:尺有所短,寸有所長(zhǎng),各取所愛(ài),萬(wàn)物生長(zhǎng)!
4. 整體流程說(shuō)明
本篇我們開(kāi)發(fā)一個(gè)商品瀏覽項(xiàng)目實(shí)例。
此處說(shuō)一個(gè)我個(gè)人的經(jīng)驗(yàn):在做一個(gè)項(xiàng)目或一個(gè)模塊的時(shí)候,不要一開(kāi)始就動(dòng)手寫(xiě)代碼,最好是謀定而后動(dòng)。
我們作為程序員,實(shí)際上是整個(gè)程序世界的總指揮。應(yīng)該先整體規(guī)劃,再實(shí)現(xiàn)局部。這種總分型的開(kāi)發(fā)方法便于我們理順?biāo)悸?,提高編碼效率!
好的,我們來(lái)思考下,實(shí)現(xiàn)商品瀏覽項(xiàng)目實(shí)例的整體流程:
可以看到,我們是先建立了控制器方法和頁(yè)面,再去實(shí)現(xiàn)其中的具體細(xì)節(jié)。這樣可以讓我們的思維保持連貫性和整體性,在做一些頁(yè)面和方法較多的項(xiàng)目時(shí),會(huì)感覺(jué)更加順暢。
5. 使用 FreeMarker
我們按整體流程,使用 FreeMarker 模板引擎,來(lái)實(shí)現(xiàn)商品瀏覽功能。
5.1 創(chuàng)建 Spring Boot 項(xiàng)目并導(dǎo)入開(kāi)發(fā)環(huán)境
使用 Spring Initializr 創(chuàng)建項(xiàng)目,Spring Boot 版本選擇 2.2.5 , Group 為 com.imooc
, Artifact 為 spring-boot-freemarker
,生成項(xiàng)目后導(dǎo)入 Eclipse 開(kāi)發(fā)環(huán)境。
5.2 在 pom.xml 中引入相關(guān)依賴(lài)
引入 Web 項(xiàng)目及 FreeMarker 模板相關(guān)的依賴(lài)項(xiàng),代碼如下:
實(shí)例:
<!-- 引入web項(xiàng)目相關(guān)依賴(lài) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
5.3 創(chuàng)建控制器方法,指向商品頁(yè)面
創(chuàng)建控制器類(lèi),由于是商品相關(guān)的控制器,所以命名為 GoodsController ,代碼如下:
實(shí)例:
/**
* 商品控制器
*/
@Controller // 標(biāo)注為控制器
public class GoodsController {
/**
* 獲取商品列表
*/
@RequestMapping("/goods") // 請(qǐng)求路徑
public String goods() {
return "goods";// 跳轉(zhuǎn)到goods.ftl頁(yè)面
}
}
我們具體解釋下該類(lèi)的作用。
- @Controller 注解標(biāo)注在 GoodsController 類(lèi)上,會(huì)為該類(lèi)注冊(cè)一個(gè)控制器組件,放入 Spring 容器中。該組件具備處理請(qǐng)求的能力,其中的方法可以響應(yīng) HTTP 請(qǐng)求;
- @RequestMapping ("/goods") 注解標(biāo)注在方法 goods () 上,所以請(qǐng)求路徑如果匹配
/goods
,則由該方法進(jìn)行處理; - 返回值是字符串
"goods"
,由于我們已經(jīng)引入 FreeMarker 依賴(lài),所以該返回值會(huì)跳轉(zhuǎn)到 goods.ftl 頁(yè)面。
Tips: 注意需要在 application.properties 文件中設(shè)置模板文件的后綴,即:
spring.freemarker.suffix=.ftl
。如果不添加該配置,直接return "goods.ftl";
會(huì)報(bào)錯(cuò)。
5.4 創(chuàng)建商品頁(yè)面
我們 resource/templates
目錄下新建商品頁(yè)面 goods.ftl
,先不必實(shí)現(xiàn)具體功能,代碼如下:
實(shí)例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商品列表</title>
</head>
<body>
商品列表
</body>
</html>
此時(shí)我們啟動(dòng)項(xiàng)目,然后訪(fǎng)問(wèn) http://127.0.0.1:8080/goods
,即可顯示對(duì)應(yīng)頁(yè)面內(nèi)容。
5.5 在控制器方法中,調(diào)用服務(wù)方法獲取商品信息,并將信息交給模板引擎處理
定義商品類(lèi) GoodsDo 用來(lái)描述商品信息,注意 Do 表示數(shù)據(jù)模型對(duì)象(Data Object),代碼如下:
實(shí)例:
/**
* 商品數(shù)據(jù)對(duì)象
*/
public class GoodsDo {
/**
* 商品名稱(chēng)
*/
private String name;
/**
* 商品價(jià)格
*/
private String price;
/**
* 商品圖片
*/
private String pic;
// 省略get set方法
}
然后我們編寫(xiě)服務(wù)類(lèi) GoodsService ,提供獲取商品列表的方法。注意此處僅僅是演示模板引擎,并不需要訪(fǎng)問(wèn)數(shù)據(jù)庫(kù),直接返回一個(gè)指定內(nèi)容的商品列表。
實(shí)例:
/**
* 商品服務(wù)
*/
@Service // 為GoodsService注冊(cè)一個(gè)組件
public class GoodsService {
public List<GoodsDo> getGoodsList() {
List<GoodsDo> list = new ArrayList<GoodsDo>();
GoodsDo goods = new GoodsDo();
goods.setName("蘋(píng)果");
goods.setPic("apple.jpg");
goods.setPrice("3.5");
list.add(goods);
return list;
}
}
此時(shí),我們的控制器就可以注入 GoodsService 類(lèi)型的組件,然后調(diào)用其方法了。
實(shí)例:
@Controller
public class GoodsController {
@Autowired
private GoodsService goodsService;// 自動(dòng)裝配
@RequestMapping("/goods") // 請(qǐng)求路徑
public String goods(Model model) {
model.addAttribute("goodsList", goodsService.getGoodsList());// 交給模板引擎處理的數(shù)據(jù)
return "goods";// 跳轉(zhuǎn)到goods.ftl頁(yè)面
}
}
注意 model.addAttribute("goodsList", goodsService.getGoodsList());
,我們將商品列表相關(guān)的數(shù)據(jù)交給模板引擎去處理。
5.6 在商品頁(yè)面通過(guò)模板引擎規(guī)則顯示商品信息
此時(shí)我們可以根據(jù) FreeMarker 模板引擎,按模板規(guī)則顯示商品信息了。
實(shí)例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商品列表</title>
</head>
<body>
<div>商品列表:</div>
<#list goodsList as item>
${item.name}--${item.price}--${item.pic}
</#list>
</body>
</html>
注意我們通過(guò) FreeMarker 的模板語(yǔ)法,輸出了商品列表信息。關(guān)于 FreeMarker 模板引擎更多的語(yǔ)法規(guī)則,感興趣的同學(xué)可以后續(xù)查閱更多資料。
5.7 測(cè)試
啟動(dòng)項(xiàng)目,打開(kāi)瀏覽器訪(fǎng)問(wèn) http://127.0.0.1:8080/goods
,即可查看輸出結(jié)果。
6. 使用 Thymeleaf
Thymeleaf 和 FreeMarker ,都是模板引擎,使用方法基本類(lèi)似。此處我們僅僅是給出一個(gè)范例,不再做過(guò)多的解釋。
6.1 創(chuàng)建 Spring Boot 項(xiàng)目并導(dǎo)入開(kāi)發(fā)環(huán)境
使用 Spring Initializr 創(chuàng)建項(xiàng)目, Spring Boot 版本選擇 2.2.5 , Group 為 com.imooc
, Artifact 為 spring-boot-thymeleaf
,生成項(xiàng)目后導(dǎo)入 Eclipse 開(kāi)發(fā)環(huán)境。
6.2 在 pom.xml 中引入相關(guān)依賴(lài)
引入 Web 項(xiàng)目及 Thymeleaf 模板相關(guān)的依賴(lài)項(xiàng)。
實(shí)例:
<!-- 引入web項(xiàng)目相關(guān)依賴(lài) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- ThymeLeaf依賴(lài) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
6.3 創(chuàng)建控制器方法,指向商品頁(yè)面
創(chuàng)建控制器類(lèi), GoodsController , Thymeleaf 直接使用 HTML 作為模板頁(yè)面,故代碼如下:
實(shí)例:
/**
* 商品控制器
*/
@Controller // 標(biāo)注為控制器
public class GoodsController {
/**
* 獲取商品列表
*/
@RequestMapping("/goods") // 請(qǐng)求路徑
public String goods() {
return "goods.html";// 跳轉(zhuǎn)到goods.html頁(yè)面
}
}
6.4 創(chuàng)建商品頁(yè)面
我們?cè)?resource/templates
目錄下新建商品頁(yè)面 goods.html
,先不必實(shí)現(xiàn)具體功能,代碼如下:
實(shí)例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商品列表</title>
</head>
<body>
商品列表
</body>
</html>
此時(shí)我們啟動(dòng)項(xiàng)目,然后訪(fǎng)問(wèn) http://127.0.0.1:8080/goods
,即可顯示對(duì)應(yīng)頁(yè)面內(nèi)容。
6.5 在控制器方法中,調(diào)用服務(wù)方法獲取商品信息,并將信息交給模板引擎處理
商品類(lèi) GoodsDo ,服務(wù)類(lèi) GoodsService ,這兩個(gè)類(lèi)與上面沒(méi)有區(qū)別直接放出代碼。
實(shí)例:
/**
* 商品數(shù)據(jù)對(duì)象
*/
public class GoodsDo {
/**
* 商品名稱(chēng)
*/
private String name;
/**
* 商品價(jià)格
*/
private String price;
/**
* 商品圖片
*/
private String pic;
// 省略get set方法
}
實(shí)例:
/**
* 商品服務(wù)
*/
@Service // 為GoodsService注冊(cè)一個(gè)組件
public class GoodsService {
public List<GoodsDo> getGoodsList() {
List<GoodsDo> list = new ArrayList<GoodsDo>();
GoodsDo goods = new GoodsDo();
goods.setName("蘋(píng)果");
goods.setPic("apple.jpg");
goods.setPrice("3.5");
list.add(goods);
return list;
}
}
好的,此時(shí)我們的控制器就可以注入 GoodsService 類(lèi)型的組件,然后調(diào)用其方法了。
實(shí)例:
@Controller
public class GoodsController {
@Autowired
private GoodsService goodsService;// 自動(dòng)裝配
@RequestMapping("/goods") // 請(qǐng)求路徑
public String goods(Model model) {
model.addAttribute("goodsList", goodsService.getGoodsList());// 交給模板引擎處理的數(shù)據(jù)
return "goods.html";// 跳轉(zhuǎn)到goods.html頁(yè)面
}
}
6.6 在商品頁(yè)面通過(guò)模板引擎規(guī)則顯示商品信息
此時(shí)我們可以根據(jù) Thymeleaf 模板引擎,按模板規(guī)則顯示商品信息了。
實(shí)例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商品列表</title>
</head>
<body>
<div>商品列表:</div>
<div th:each="item:${goodsList}">
<span th:text="${item.name}"></span>
<span th:text="${item.price}"></span>
<span th:text="${item.pic}"></span>
</div>
</body>
</html>
注意我們通過(guò) Thymeleaf 的模板語(yǔ)法,輸出了商品列表信息。關(guān)于 Thymeleaf 模板引擎更多的語(yǔ)法規(guī)則,感興趣的同學(xué)可以后續(xù)查閱更多資料。
6.7 測(cè)試
啟動(dòng)項(xiàng)目,打開(kāi)瀏覽器訪(fǎng)問(wèn) http://127.0.0.1:8080/goods
,即可查看輸出結(jié)果。
到此,大家基本上也能發(fā)現(xiàn),這兩種方式除了模板頁(yè)面文件內(nèi)容不同,其他地方基本都是一模一樣的。
也就是說(shuō),模板引擎主要負(fù)責(zé)通過(guò)一些模板標(biāo)簽,將控制器返回的數(shù)據(jù)解析為網(wǎng)頁(yè)。
7. 使用 JSP
注意 Spring Boot 官方已經(jīng)不推薦使用 JSP 了,確實(shí)操作起來(lái)也比較麻煩。但是由于 JSP 用戶(hù)體量還是比較大的,所以此處還是簡(jiǎn)單演示下,開(kāi)發(fā)步驟與 FreeMarker / Thymeleaf 基本一致。
7.1 創(chuàng)建 Spring Boot 項(xiàng)目并導(dǎo)入開(kāi)發(fā)環(huán)境
使用 Spring Initializr 創(chuàng)建項(xiàng)目, Spring Boot 版本選擇 2.2.5 , Group 為 com.imooc
, Artifact 為 spring-boot-jsp
,生成項(xiàng)目后導(dǎo)入 Eclipse 開(kāi)發(fā)環(huán)境。
7.2 在 pom.xml 中引入相關(guān)依賴(lài)
引入 Web 項(xiàng)目及 JSP 模板相關(guān)的依賴(lài)項(xiàng)。
實(shí)例:
<!-- 添加web開(kāi)發(fā)功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--內(nèi)嵌的tomcat支持模塊 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- 對(duì)jstl的支持 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
7.3 創(chuàng)建控制器方法,指向商品頁(yè)面
創(chuàng)建控制器類(lèi), GoodsController ,代碼如下:
實(shí)例:
/**
* 商品控制器
*/
@Controller // 標(biāo)注為控制器
public class GoodsController {
/**
* 獲取商品列表
*/
@RequestMapping("/goods") // 請(qǐng)求路徑
public String goods() {
return "goods";// 跳轉(zhuǎn)到goods.jsp頁(yè)面
}
}
7.4 創(chuàng)建商品頁(yè)面
手工添加 src/main/webapp
及子目錄如下,同時(shí)目錄下放一個(gè) goods.jsp 用于測(cè)試。注意該目錄是一個(gè) Source Folder 源代碼目錄,不是普通文件夾目錄。
實(shí)例:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商品列表</title>
</head>
<body>商品列表
</body>
</html>
注意,我們還需要添加一個(gè)視圖解析器,實(shí)現(xiàn) JSP 頁(yè)面往指定目錄跳轉(zhuǎn)。
實(shí)例:
@SpringBootApplication
public class SpringBootJspApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootJspApplication.class, args);
}
@Bean // 注冊(cè)視圖解析器
public InternalResourceViewResolver setupViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/jsp/");// 自動(dòng)添加前綴
resolver.setSuffix(".jsp");// 自動(dòng)添加后綴
return resolver;
}
}
此時(shí)我們啟動(dòng)項(xiàng)目,然后訪(fǎng)問(wèn) http://127.0.0.1:8080/goods
,即可顯示對(duì)應(yīng)頁(yè)面內(nèi)容。
7.5 在控制器方法中,調(diào)用服務(wù)方法獲取商品信息,并將信息交給模板引擎處理
商品類(lèi) GoodsDo ,服務(wù)類(lèi) GoodsService ,這兩個(gè)類(lèi)與上面沒(méi)有區(qū)別直接放出代碼。
實(shí)例:
/**
* 商品數(shù)據(jù)對(duì)象
*/
public class GoodsDo {
/**
* 商品名稱(chēng)
*/
private String name;
/**
* 商品價(jià)格
*/
private String price;
/**
* 商品圖片
*/
private String pic;
// 省略get set方法
}
實(shí)例:
/**
* 商品服務(wù)
*/
@Service // 為GoodsService注冊(cè)一個(gè)組件
public class GoodsService {
public List<GoodsDo> getGoodsList() {
List<GoodsDo> list = new ArrayList<GoodsDo>();
GoodsDo goods = new GoodsDo();
goods.setName("蘋(píng)果");
goods.setPic("apple.jpg");
goods.setPrice("3.5");
list.add(goods);
return list;
}
}
好的,此時(shí)我們的控制器就可以注入 GoodsService 類(lèi)型的組件,然后調(diào)用其方法了。
實(shí)例:
@Controller
public class GoodsController {
@Autowired
private GoodsService goodsService;// 自動(dòng)裝配
@RequestMapping("/goods") // 請(qǐng)求路徑
public String goods(Model model) {
model.addAttribute("goodsList", goodsService.getGoodsList());// 交給模板引擎處理的數(shù)據(jù)
return "goods";// 跳轉(zhuǎn)到goods.jsp
}
}
7.6 在商品頁(yè)面通過(guò)模板引擎規(guī)則顯示商品信息
此時(shí)我們可以根據(jù) JSP 模板引擎,按模板規(guī)則顯示商品信息了。
實(shí)例:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商品列表</title>
</head>
<body>
<div>商品列表:</div>
<c:forEach var="item" items="${goodsList}">
${item.name}--${item.price}--${item.pic}
</c:forEach>
</body>
</html>
注意我們通過(guò) JSP 的模板語(yǔ)法,輸出了商品列表信息。關(guān)于 JSP 模板引擎更多的語(yǔ)法規(guī)則,感興趣的同學(xué)可以后續(xù)查閱更多資料。
7.7 測(cè)試
啟動(dòng)項(xiàng)目,打開(kāi)瀏覽器訪(fǎng)問(wèn) http://127.0.0.1:8080/goods
,即可查看輸出結(jié)果。
8. 小結(jié)
最后大家應(yīng)該也發(fā)現(xiàn)了, FreeMarker 和 Thymeleaf 的用法幾乎是一模一樣的,而 JSP 還需要手工添加一些目錄和配置。
三種方式各有優(yōu)劣, FreeMarker 模板語(yǔ)法比較簡(jiǎn)潔, Thymeleaf 可以直接使用 HTML 作為模板文件, JSP 用戶(hù)群體廣泛。
但是三種方式,都是一種模板引擎而已,將控制器返回?cái)?shù)據(jù)轉(zhuǎn)化為 HTML 頁(yè)面顯示,本質(zhì)上沒(méi)啥區(qū)別,大家對(duì)模板引擎有一個(gè)了解即可。