SpringBoot?Web開(kāi)發(fā)之請(qǐng)求響應(yīng)、分層解耦問(wèn)題記錄
一.請(qǐng)求響應(yīng)概述
1.Servlet
在 Spring Boot 的 Web 請(qǐng)求響應(yīng)處理中,Servlet 起著關(guān)鍵的作用。
Servlet 是 Java Web 開(kāi)發(fā)中的基本組件,主要負(fù)責(zé)處理客戶端的請(qǐng)求并生成響應(yīng)。
具體來(lái)說(shuō),它具有以下重要作用:
- 接收請(qǐng)求:Servlet 能夠接收來(lái)自客戶端(如瀏覽器)發(fā)送的 HTTP 請(qǐng)求。
- 處理請(qǐng)求:在接收到請(qǐng)求后,執(zhí)行相應(yīng)的業(yè)務(wù)邏輯處理。這可能包括與數(shù)據(jù)庫(kù)交互、進(jìn)行數(shù)據(jù)計(jì)算、驗(yàn)證用戶輸入等操作。
- 控制流程:根據(jù)請(qǐng)求的類型和參數(shù),決定后續(xù)的處理流程和響應(yīng)方式。
- 生成響應(yīng):處理完請(qǐng)求后,生成要返回給客戶端的響應(yīng)數(shù)據(jù)。
- 與其他組件協(xié)作:可以與其他的 Java 類、服務(wù)或組件進(jìn)行協(xié)作,以完成復(fù)雜的業(yè)務(wù)功能。
例如,在一個(gè)用戶登錄的場(chǎng)景中,Servlet 接收到用戶提交的登錄表單數(shù)據(jù),然后驗(yàn)證用戶名和密碼是否正確。如果正確,生成一個(gè)成功登錄的響應(yīng);如果不正確,生成一個(gè)錯(cuò)誤提示的響應(yīng)。
在 Spring Boot 中,DispatcherServlet 是一個(gè)特殊的 Servlet,它負(fù)責(zé)協(xié)調(diào)和分發(fā)請(qǐng)求到具體的控制器(Controller)進(jìn)行處理,使得整個(gè)請(qǐng)求處理流程更加清晰和高效。
Dispatcher:調(diào)度員;調(diào)度程序;發(fā)送器
2.DispatcherServlet
在 Spring Boot 中,DispatcherServlet 是一個(gè)核心組件,起著非常重要的作用
DispatcherServlet 主要負(fù)責(zé)接收客戶端的請(qǐng)求,并將請(qǐng)求分發(fā)給相應(yīng)的處理器(Handler)進(jìn)行處理。它是 Spring Web MVC 框架的前端控制器。
其工作流程大致如下:
當(dāng)客戶端發(fā)送一個(gè) HTTP 請(qǐng)求到應(yīng)用程序時(shí),DispatcherServlet 首先會(huì)接收到這個(gè)請(qǐng)求。然后,它會(huì)根據(jù)請(qǐng)求的 URL 和其他相關(guān)信息,通過(guò)一系列的映射規(guī)則,來(lái)確定應(yīng)該調(diào)用哪個(gè)控制器(Controller)來(lái)處理這個(gè)請(qǐng)求。
在確定了控制器之后,DispatcherServlet 會(huì)將請(qǐng)求傳遞給對(duì)應(yīng)的控制器方法進(jìn)行處理??刂破魈幚硗暾?qǐng)求后,通常會(huì)返回一個(gè)模型(Model)和視圖(View)的信息。
DispatcherServlet 接著會(huì)根據(jù)返回的視圖信息,選擇合適的視圖解析器(View Resolver)來(lái)將模型數(shù)據(jù)渲染成最終的響應(yīng)頁(yè)面,并將響應(yīng)返回給客戶端。
例如,如果有一個(gè)用戶請(qǐng)求獲取商品列表的頁(yè)面,DispatcherServlet 會(huì)找到處理該請(qǐng)求的商品控制器,然后由控制器獲取商品數(shù)據(jù)并返回給 DispatcherServlet ,DispatcherServlet 再通過(guò)視圖解析器將數(shù)據(jù)展示在相應(yīng)的頁(yè)面上。
總之,DispatcherServlet 是 Spring Boot 中實(shí)現(xiàn) Web 應(yīng)用請(qǐng)求處理和響應(yīng)生成的關(guān)鍵環(huán)節(jié),確保了整個(gè) Web 應(yīng)用的流暢運(yùn)行和高效響應(yīng)。

DispatcherServlet 類繼承了Servlet 接口
3.請(qǐng)求響應(yīng)工作概圖



DispatcherServlet會(huì)根據(jù)請(qǐng)求調(diào)度Controller控制器,然后獲得響應(yīng)的數(shù)據(jù)

(1)HttpServletRequest 是 Java Servlet 規(guī)范中定義的一個(gè)接口,用于表示客戶端發(fā)送到服務(wù)器的 HTTP 請(qǐng)求。
它包含了大量與請(qǐng)求相關(guān)的信息和方法:
請(qǐng)求方法:例如 GET、POST、PUT、DELETE 等,通過(guò) getMethod() 方法獲取。
請(qǐng)求 URL:可以通過(guò) getRequestURI() 方法獲取請(qǐng)求的資源路徑,getQueryString() 方法獲取查詢字符串。
請(qǐng)求頭信息:如 User-Agent(客戶端瀏覽器和操作系統(tǒng)信息)、Content-Type(請(qǐng)求體的數(shù)據(jù)類型)等,使用 getHeader(String name) 方法獲取指定的請(qǐng)求頭。
請(qǐng)求參數(shù):包括表單提交的參數(shù)、URL 中的參數(shù)等,通過(guò) getParameter(String name) 方法獲取單個(gè)參數(shù)值,getParameterValues(String name) 方法獲取具有多個(gè)值的參數(shù)。
例如,在一個(gè)登錄頁(yè)面中,用戶輸入用戶名和密碼后提交表單,服務(wù)器端可以通過(guò) HttpServletRequest 來(lái)獲取用戶名和密碼的參數(shù)值,進(jìn)行后續(xù)的驗(yàn)證處理。
另外,如果客戶端發(fā)送了一個(gè)帶有特定 Cookie 的請(qǐng)求,服務(wù)器可以通過(guò) getCookies() 方法獲取這些 Cookie 信息,從而實(shí)現(xiàn)會(huì)話跟蹤等功能。
HttpServletRequest 為服務(wù)器端處理客戶端的 HTTP 請(qǐng)求提供了豐富的信息和操作方法,是構(gòu)建 Web 應(yīng)用的重要組成部分。
(2)HttpServletResponse 是 Java Servlet 規(guī)范中定義的一個(gè)接口,用于表示服務(wù)器對(duì)客戶端 HTTP 請(qǐng)求的響應(yīng)。
它包含了一系列方法,用于設(shè)置響應(yīng)的狀態(tài)碼、響應(yīng)頭信息、響應(yīng)體內(nèi)容等。
一些常見(jiàn)的方法包括:
setStatus(int status):設(shè)置響應(yīng)的狀態(tài)碼,例如200表示成功,404表示未找到資源,500表示服務(wù)器內(nèi)部錯(cuò)誤等。setHeader(String name, String value):設(shè)置響應(yīng)頭信息,如設(shè)置Content-Type來(lái)指定響應(yīng)體的數(shù)據(jù)類型。getWriter():獲取一個(gè)PrintWriter對(duì)象,用于向響應(yīng)體中寫(xiě)入字符數(shù)據(jù)。getOutputStream():獲取一個(gè)ServletOutputStream對(duì)象,用于向響應(yīng)體中寫(xiě)入二進(jìn)制數(shù)據(jù)。
4.BS/CS架構(gòu)
BS即“Browser/Server”(瀏覽器/服務(wù)器模式) :在這種模式下,用戶通過(guò)瀏覽器訪問(wèn)服務(wù)器上的應(yīng)用程序。客戶端主要負(fù)責(zé)顯示數(shù)據(jù)和接收用戶輸入,而大部分的業(yè)務(wù)邏輯和數(shù)據(jù)處理都在服務(wù)器端完成。例如常見(jiàn)的各類網(wǎng)站、在線辦公系統(tǒng)等。其優(yōu)點(diǎn)包括易于維護(hù)和升級(jí)、跨平臺(tái)性好、用戶使用方便等。
CS即“Client/Server”(客戶端/服務(wù)器模式) :這種模式下,需要在客戶端安裝專門的應(yīng)用程序來(lái)與服務(wù)器進(jìn)行交互??蛻舳撕头?wù)器端都承擔(dān)一定的業(yè)務(wù)邏輯和數(shù)據(jù)處理任務(wù)。例如一些大型的游戲客戶端、企業(yè)級(jí)的本地應(yīng)用程序等。其優(yōu)點(diǎn)可能包括響應(yīng)速度快、能充分利用本地資源等,但缺點(diǎn)是部署和維護(hù)成本較高,客戶端的更新較為復(fù)雜。
比如在線購(gòu)物網(wǎng)站通常采用 BS 架構(gòu),用戶通過(guò)瀏覽器就能訪問(wèn)和操作;而像一些專業(yè)的圖形設(shè)計(jì)軟件可能采用 CS 架構(gòu),以充分發(fā)揮本地計(jì)算機(jī)的性能。

二.API測(cè)試工具
API 測(cè)試工具是專門用于對(duì)應(yīng)用程序編程接口(API)進(jìn)行測(cè)試和驗(yàn)證的軟件工具。
這些工具的主要目的是幫助開(kāi)發(fā)人員、測(cè)試人員和質(zhì)量保證團(tuán)隊(duì)確保 API 按照預(yù)期工作,能夠正確處理輸入請(qǐng)求并返回準(zhǔn)確、有效的響應(yīng)。
ApiPost:
優(yōu)勢(shì):
- 提供中文界面,對(duì)國(guó)內(nèi)用戶更友好。
- 接口文檔生成和分享功能便捷,適合團(tuán)隊(duì)協(xié)作。
- 支持離線使用,適用于特殊網(wǎng)絡(luò)環(huán)境。
適用場(chǎng)景:
- 適合國(guó)內(nèi)開(kāi)發(fā)團(tuán)隊(duì),尤其是需要高效協(xié)作和生成詳細(xì)接口文檔的項(xiàng)目。
ApiFox:
- 優(yōu)勢(shì):
- 集 API 文檔、調(diào)試、Mock、自動(dòng)化測(cè)試為一體。
- 支持多種數(shù)據(jù)格式的導(dǎo)入和導(dǎo)出。
- 適用場(chǎng)景:
- 適用于需要全面管理 API 全生命周期的項(xiàng)目。
Postman:
- 優(yōu)勢(shì):
- 應(yīng)用廣泛,社區(qū)活躍,資源豐富。
- 支持豐富的插件擴(kuò)展。
- 適用場(chǎng)景:
- 適合個(gè)人開(kāi)發(fā)者和大型國(guó)際化團(tuán)隊(duì)。
雖然Postman很好,但是不用魔法我打不開(kāi),注冊(cè)登錄不了(囧)
所以我選擇Apipost

三.請(qǐng)求
1.簡(jiǎn)單參數(shù)
簡(jiǎn)單參數(shù)通常指的是基本數(shù)據(jù)類型的參數(shù),例如整數(shù)、浮點(diǎn)數(shù)、布爾值、字符或字符串等。這些參數(shù)通常是獨(dú)立的值,直接傳遞給函數(shù)或方法進(jìn)行處理。
(1)原始方式(不推薦)
通過(guò) HttpServletRequest 對(duì)象來(lái)獲取原始的請(qǐng)求信息

get方式
package com.example.demos.controllers;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
public class RequestController {
//原始方式
@RequestMapping("/simpleParam")
public String simpleParam(HttpServletRequest request){
//獲取請(qǐng)求參數(shù)
String name=request.getParameter("name");
String ageStr = request.getParameter("age");
int age=Integer.parseInt(ageStr);//類型轉(zhuǎn)換
System.out.println(name+":"+age);
return "ok";
}
}
因?yàn)榉爆?,且要手?dòng)進(jìn)行類型轉(zhuǎn)換,所以一般不用
(2)Spring Boot方式
將請(qǐng)求參數(shù)名寫(xiě)在形參列表里,spring boot會(huì)自動(dòng)轉(zhuǎn)換類型

@RequestMapping("/simpleParam")
public String simpleParam(String name,Integer age){
System.out.println(name+":"+age);
return "ok";若是post方式,要將參數(shù)的值寫(xiě)在 body里,如下

若方法形參和請(qǐng)求參數(shù)名不一致,不會(huì)報(bào)錯(cuò),會(huì)接收到空(NULL)
可以使用映射使兩個(gè)名對(duì)應(yīng)上



@RequestParam 是 Spring 框架中用于處理 HTTP 請(qǐng)求參數(shù)的注解。
當(dāng)在控制器的方法參數(shù)上使用 @RequestParam 時(shí),可以將請(qǐng)求中的參數(shù)值綁定到方法的參數(shù)上。
@RequestParam 還可以設(shè)置一些屬性,例如:
required:指定參數(shù)是否必需,默認(rèn)值為true。如果設(shè)置為false,當(dāng)請(qǐng)求中沒(méi)有該參數(shù)時(shí),不會(huì)拋出異常。defaultValue:當(dāng)請(qǐng)求中沒(méi)有該參數(shù)時(shí)使用的默認(rèn)值。
2 .實(shí)體參數(shù)
實(shí)體參數(shù)通常指的是一個(gè)具有復(fù)雜結(jié)構(gòu)或包含多個(gè)相關(guān)屬性的對(duì)象或數(shù)據(jù)結(jié)構(gòu)

POJO(Plain Old Java Object)即普通的 Java 對(duì)象 。
{
與 POJO(Plain Old Java Object,普通 Java 對(duì)象)相對(duì)應(yīng)的概念包括
DTO(Data Transfer Object,數(shù)據(jù)傳輸對(duì)象):主要用于在不同層或不同系統(tǒng)之間傳輸數(shù)據(jù),通常只包含必要的數(shù)據(jù)字段,并且這些字段通常是只讀的。VO(Value Object,值對(duì)象):用于表示不可變的值,通常只包含屬性和訪問(wèn)這些屬性的方法,并且沒(méi)有任何行為邏輯。Entity(實(shí)體):在數(shù)據(jù)庫(kù)相關(guān)的設(shè)計(jì)中,用于表示數(shù)據(jù)庫(kù)中的表對(duì)應(yīng)的對(duì)象,通常與數(shù)據(jù)庫(kù)中的記錄相對(duì)應(yīng),并包含持久化相關(guān)的邏輯。
}
POJO user對(duì)象
package com.example.demos.pojos;
public class User {
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
/**
* 獲取
* @return name
*/
public String getName() {
return name;
}
/**
* 設(shè)置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 獲取
* @return age
*/
public Integer getAge() {
return age;
}
/**
* 設(shè)置
* @param age
*/
public void setAge(Integer age) {
this.age = age;
}
public String toString() {
return "User{name = " + name + ", age = " + age + "}";
}
}@RequestMapping("/simpleParam")
public String simpleParam(User user){
System.out.println(user.getAge()+ user.getName());
return "ok";
}復(fù)雜實(shí)體類型


3.數(shù)組集合參數(shù)



4.日期參數(shù)

5.JSON參數(shù)

@RequestBody 是 Spring 框架中用于處理 HTTP 請(qǐng)求體的注解。
當(dāng)在控制器的方法參數(shù)上使用 @RequestBody 時(shí),它會(huì)將 HTTP 請(qǐng)求體中的數(shù)據(jù)(通常是 JSON、XML 等格式)綁定到方法的參數(shù)對(duì)象上。
他還有將數(shù)據(jù)響應(yīng)給瀏覽器的功能,見(jiàn)后文
6.路徑參數(shù)
路徑參數(shù)是在 URL 路徑中傳遞的參數(shù)。
例如,假設(shè)有一個(gè) URL 類似于 https://example.com/user/123 ,其中的 123 就是一個(gè)路徑參數(shù)。在 Web 開(kāi)發(fā)中,服務(wù)器端可以獲取這個(gè)路徑參數(shù),并根據(jù)其值進(jìn)行相應(yīng)的處理。

多個(gè)路徑參數(shù)樣式

路徑參數(shù)具有以下重要作用
- 精確資源定位
- 能夠準(zhǔn)確地指定要訪問(wèn)的特定資源。例如,在一個(gè)博客系統(tǒng)中,/post/123 中的 123 可以準(zhǔn)確指向特定的文章。
- 簡(jiǎn)化 URL 結(jié)構(gòu)
- 使 URL 看起來(lái)更簡(jiǎn)潔和有組織,而不是通過(guò)大量的查詢參數(shù)來(lái)傳遞關(guān)鍵信息。
- 提高路由效率
- 服務(wù)器端可以基于路徑參數(shù)快速進(jìn)行路由決策,提高請(qǐng)求處理的效率。
- 增強(qiáng)用戶體驗(yàn)
- 對(duì)于用戶來(lái)說(shuō),直觀的路徑參數(shù)更容易理解和記憶。
- 實(shí)現(xiàn)動(dòng)態(tài)內(nèi)容展示
- 根據(jù)不同的路徑參數(shù),服務(wù)器可以動(dòng)態(tài)地生成和返回不同的內(nèi)容。
- 便于權(quán)限控制和訪問(wèn)管理
- 可以基于路徑參數(shù)來(lái)設(shè)置不同的權(quán)限規(guī)則,控制對(duì)特定資源的訪問(wèn)。
例如,在一個(gè)在線教育平臺(tái),/course/101/lesson/5 這樣的路徑參數(shù)能夠清晰地標(biāo)識(shí)特定的課程和課程中的特定章節(jié),服務(wù)器可以據(jù)此提供準(zhǔn)確的教學(xué)內(nèi)容,并進(jìn)行相應(yīng)的權(quán)限驗(yàn)證。
總結(jié)

四.響應(yīng)
1.響應(yīng)的實(shí)現(xiàn)和過(guò)程

基本上都使用 @RestController 注解的控s制器方法來(lái)直接返回?cái)?shù)據(jù)
如下圖,@RestController 注解包含了 @ResponseBody注解


響應(yīng)的數(shù)據(jù)若是較復(fù)雜,以JSON格式傳遞
但這樣還是不方便管理和后期維護(hù),所以有統(tǒng)一的響應(yīng)格式
2.統(tǒng)一響應(yīng)格式
常見(jiàn)的統(tǒng)一響應(yīng)格式可以包含以下幾個(gè)部分:
status:表示請(qǐng)求的處理狀態(tài),通常是一個(gè)整數(shù),例如1表示成功,0表示失敗。message:對(duì)狀態(tài)的簡(jiǎn)要描述信息,解釋請(qǐng)求處理的結(jié)果。data:實(shí)際要返回的數(shù)據(jù)內(nèi)容,可以是對(duì)象、數(shù)組、字符串等各種數(shù)據(jù)類型。

Result封裝類代碼
/**
* 統(tǒng)一響應(yīng)結(jié)果封裝類
*/
public class Result {
private Integer code ;//1 成功 , 0 失敗
private String msg; //提示信息
private Object data; //數(shù)據(jù) data
public Result() {
}
public Result(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Result success(Object data){
return new Result(1, "success", data);
}
public static Result success(){
return new Result(1, "success", null);
}
public static Result error(String msg){
return new Result(0, msg, null);
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}封裝了類的靜態(tài)方法可以直接調(diào)用封裝的success()方法 快速返回?cái)?shù)據(jù)。
例如
@RequestMapping("/simpleParam")
public Result simpleParam(User user){
System.out.println(user.getAge()+ user.getName());
//return new Result(1,"success","Hello");
return Result.success("Hello,SpringBoot");
}瀏覽器接收到的JSON數(shù)據(jù):

五.樣例案例 TIP DOM4J
DOM4J 是一個(gè) Java 的 XML 操作庫(kù)。
它具有以下特點(diǎn)和優(yōu)勢(shì):
- 強(qiáng)大的解析功能:能夠有效地解析和處理復(fù)雜的 XML 文檔。
- 靈活的操作:支持對(duì) XML 節(jié)點(diǎn)的創(chuàng)建、修改、刪除、查詢等操作。
- 易于使用:提供了簡(jiǎn)潔直觀的 API,使得開(kāi)發(fā)人員能夠輕松上手。
作用:
- XML 文檔解析
能夠讀取和解析 XML 文檔,將其轉(zhuǎn)換為易于操作的 Java 對(duì)象結(jié)構(gòu)。
- 節(jié)點(diǎn)操作
可以方便地訪問(wèn)、添加、修改和刪除 XML 文檔中的節(jié)點(diǎn)(元素、屬性、文本等)。
- 數(shù)據(jù)提取
從 XML 文檔中提取所需的數(shù)據(jù),例如特定元素的值或?qū)傩缘闹怠?/p>
- 構(gòu)建 XML 文檔
能夠從零開(kāi)始創(chuàng)建新的 XML 文檔,并按照指定的結(jié)構(gòu)添加內(nèi)容。
- 遍歷文檔
支持對(duì) XML 文檔進(jìn)行深度優(yōu)先或廣度優(yōu)先的遍歷,以便處理文檔中的各個(gè)部分。
- 與其他系統(tǒng)集成
在需要與基于 XML 的外部系統(tǒng)進(jìn)行數(shù)據(jù)交互時(shí),DOM4J 可以幫助進(jìn)行數(shù)據(jù)的轉(zhuǎn)換和處理。 案例
- 實(shí)現(xiàn)聯(lián)系前后端展示一個(gè)頁(yè)面
相關(guān)數(shù)據(jù)存儲(chǔ)在 一個(gè)XML文件里面
因?yàn)橐褂肈OMJ4解析XML對(duì)象,導(dǎo)入DOMJ4依賴
相關(guān)目錄說(shuō)明

相關(guān)代碼
(1)本項(xiàng)目構(gòu)建的XML解析工具類
package com.example.springbootwebpractice.utils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class XmlParserUtils {
public static <T> List<T> parse(String file , Class<T> targetClass) {
ArrayList<T> list = new ArrayList<T>(); //封裝解析出來(lái)的數(shù)據(jù)
try {
//1.獲取一個(gè)解析器對(duì)象
SAXReader saxReader = new SAXReader();
//2.利用解析器把xml文件加載到內(nèi)存中,并返回一個(gè)文檔對(duì)象
Document document = saxReader.read(new File(file));
//3.獲取到根標(biāo)簽
Element rootElement = document.getRootElement();
//4.通過(guò)根標(biāo)簽來(lái)獲取 user 標(biāo)簽
List<Element> elements = rootElement.elements("emp");
//5.遍歷集合,得到每一個(gè) user 標(biāo)簽
for (Element element : elements) {
//獲取 name 屬性
String name = element.element("name").getText();
//獲取 age 屬性
String age = element.element("age").getText();
//獲取 image 屬性
String image = element.element("image").getText();
//獲取 gender 屬性
String gender = element.element("gender").getText();
//獲取 job 屬性
String job = element.element("job").getText();
//組裝數(shù)據(jù)
Constructor<T> constructor = targetClass.getDeclaredConstructor(String.class, Integer.class, String.class, String.class, String.class);
constructor.setAccessible(true);
T object = constructor.newInstance(name, Integer.parseInt(age), image, gender, job);
list.add(object);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}(2) Emp pojo對(duì)象
package com.example.springbootwebpractice.pojo;
public class Emp {
private String name;
private Integer age;
private String image;
private String gender;
private String job;
public Emp() {
}
public Emp(String name, Integer age, String image, String gender, String job) {
this.name = name;
this.age = age;
this.image = image;
this.gender = gender;
this.job = job;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", age=" + age +
", image='" + image + '\'' +
", gender='" + gender + '\'' +
", job='" + job + '\'' +
'}';
}
}(3)EmpController 控制器
package com.example.springbootwebpractice.controller;
import com.example.springbootwebpractice.pojo.Emp;
import com.example.springbootwebpractice.pojo.Result;
import com.example.springbootwebpractice.utils.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Iterator;
import java.util.List;
@RestController
public class EmpController {
@RequestMapping("/listEmp")
public Result list(){
//1.加載并解析xml文件
String file=this.getClass().getClassLoader().getResource("emp.xml").getFile();
System.out.println(file);
List<Emp> emplist=XmlParserUtils.parse(file,Emp.class);
//2.對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換處理-gender,job
Iterator<Emp> it=emplist.iterator();
while (it.hasNext()){
String gender = it.next().getGender();
if(gender.equals("1")){
it.next().setGender("男");
}else if(gender.equals("2")){
it.next().setGender("女");
}
String job= it.next().getJob();
if(job.equals("1")){
it.next().setJob("講師");
}else if(job.equals("2")){
it.next().setJob("班主任");
}else if(job.equals("3")){
it.next().setJob("就業(yè)指導(dǎo)");
}
}
//3.響應(yīng)數(shù)據(jù)
return Result.success(emplist);
}
}(4)統(tǒng)一響應(yīng)格式 Result
代碼見(jiàn)上文
(5)部分前端代碼
基于Vue框架和Axious
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>員工信息</title>
</head>
<link rel="stylesheet" href="element-ui/index.css" rel="external nofollow" >
<script src="./js/vue.js"></script>
<script src="./element-ui/index.js"></script>
<script src="./js/axios-0.18.0.js"></script>
<body>
<h1 align="center">員工信息列表展示</h1>
<div id="app">
<el-table :data="tableData" style="width: 100%" stripe border >
<el-table-column prop="name" label="姓名" align="center" min-width="20%"></el-table-column>
<el-table-column prop="age" label="年齡" align="center" min-width="20%"></el-table-column>
<el-table-column label="圖像" align="center" min-width="20%">
<template slot-scope="scope">
<el-image :src="scope.row.image" style="width: 80px; height: 50px;"></el-image>
</template>
</el-table-column>
<el-table-column prop="gender" label="性別" align="center" min-width="20%"></el-table-column>
<el-table-column prop="job" label="職位" align="center" min-width="20%"></el-table-column>
</el-table>
</div>
</body>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>
<script>
new Vue({
el: "#app",
data() {
return {
tableData: []
}
},
mounted(){
axios.get('/listEmp').then(res=>{
if(res.data.code){
this.tableData = res.data.data;
}
});
},
methods: {
}
});
</script>
</html>效果
實(shí)時(shí)響應(yīng)

前端頁(yè)面

注意:這里訪問(wèn)的連接不是直接訪問(wèn)后端,而是訪問(wèn)前端,然后前通過(guò)axios異步訪問(wèn)后端,后端再發(fā)送給前端,隨即渲染展示到頁(yè)面
瀏覽器先向服務(wù)器請(qǐng)求頁(yè)面emp.html,掛載時(shí)頁(yè)面的鉤子方法mounted根據(jù)數(shù)據(jù)地址/listEmp自動(dòng)向服務(wù)器申請(qǐng)數(shù)據(jù)
六.分層解耦引入和概述

目前所有代碼寫(xiě)在一個(gè)控制器里
復(fù)用性差,難以維護(hù)
單一職責(zé)原則
單一職責(zé)原則(Single Responsibility Principle,簡(jiǎn)稱 SRP) 是面向?qū)ο缶幊讨械囊粋€(gè)重要原則。它指出:一個(gè)類應(yīng)該只有一個(gè)引起它變化的原因。
這意味著一個(gè)類應(yīng)該專注于完成一項(xiàng)特定的任務(wù)或職責(zé),而不應(yīng)該承擔(dān)過(guò)多不同類型的職責(zé)。
例如,假設(shè)有一個(gè) Employee 類,如果它既負(fù)責(zé)員工的基本信息管理(如姓名、工號(hào)等),又負(fù)責(zé)計(jì)算員工的工資和績(jī)效,那么就違反了單一職責(zé)原則。
更好的做法是將員工信息管理和工資績(jī)效計(jì)算分別放在不同的類中,比如 EmployeeInfo 類和 EmployeeSalaryCalculator 類。
三層架構(gòu)

三層架構(gòu)通常包括表現(xiàn)層(Presentation Layer)、業(yè)務(wù)邏輯層(Business Logic Layer)和數(shù)據(jù)訪問(wèn)層(Data Access Layer)。
表現(xiàn)層:
- 通常由 JSP、Servlet、Thymeleaf 模板等技術(shù)實(shí)現(xiàn)。
- 負(fù)責(zé)接收用戶的請(qǐng)求,并將處理結(jié)果以網(wǎng)頁(yè)、JSON 數(shù)據(jù)等形式返回給用戶。(控制層,請(qǐng)求和響應(yīng))
- 例如,用戶在網(wǎng)頁(yè)上提交表單,表現(xiàn)層會(huì)獲取這些表單數(shù)據(jù),并將其傳遞給業(yè)務(wù)邏輯層進(jìn)行處理。
業(yè)務(wù)邏輯層(service):
- 由一系列的 Java 類組成,處理具體的業(yè)務(wù)邏輯。
- 例如,在一個(gè)電商系統(tǒng)中,訂單的生成、庫(kù)存的扣減、用戶積分的計(jì)算等業(yè)務(wù)邏輯都在這一層實(shí)現(xiàn)。
- 業(yè)務(wù)邏輯層接收表現(xiàn)層傳來(lái)的數(shù)據(jù),進(jìn)行處理后,再調(diào)用數(shù)據(jù)訪問(wèn)層獲取或更新數(shù)據(jù)。
數(shù)據(jù)訪問(wèn)層(dao):
負(fù)責(zé)與數(shù)據(jù)庫(kù)進(jìn)行交互,執(zhí)行數(shù)據(jù)的增刪改查操作。通常使用 JDBC、MyBatis、Hibernate 等技術(shù)來(lái)實(shí)現(xiàn)。數(shù)據(jù)訪問(wèn)層將數(shù)據(jù)庫(kù)中的數(shù)據(jù)提取出來(lái),轉(zhuǎn)化為業(yè)務(wù)邏輯層能夠處理的對(duì)象,或者將業(yè)務(wù)邏輯層處理后的數(shù)據(jù)保存到數(shù)據(jù)庫(kù)中。
(1)數(shù)據(jù)訪問(wèn)層(Dao)
在編寫(xiě) DAO(Data Access Object,數(shù)據(jù)訪問(wèn)對(duì)象)包程序時(shí)要面向接口編程:
1.解耦和靈活性:通過(guò)定義接口,可以將數(shù)據(jù)訪問(wèn)的具體實(shí)現(xiàn)與使用數(shù)據(jù)訪問(wèn)的其他部分代碼解耦。這意味著如果需要更改數(shù)據(jù)存儲(chǔ)方式(例如從數(shù)據(jù)庫(kù)切換到文件存儲(chǔ)或云存儲(chǔ)),只需要更改實(shí)現(xiàn)接口的具體類,而無(wú)需修改使用數(shù)據(jù)訪問(wèn)的業(yè)務(wù)邏輯代碼。
例如,如果最初使用的是關(guān)系型數(shù)據(jù)庫(kù)的 DAO 實(shí)現(xiàn),后來(lái)需要切換為 NoSQL 數(shù)據(jù)庫(kù),只需創(chuàng)建新的符合接口的 NoSQL 實(shí)現(xiàn)類,而調(diào)用方代碼無(wú)需更改。
2.代碼的可維護(hù)性:接口定義了明確的方法簽名和功能規(guī)范,使得其他開(kāi)發(fā)者能夠清晰地了解 DAO 應(yīng)該提供的功能。這有助于提高代碼的可讀性和可理解性,從而更易于維護(hù)。
比如,新的開(kāi)發(fā)者加入項(xiàng)目,通過(guò)查看接口就能快速了解數(shù)據(jù)訪問(wèn)的相關(guān)操作。
3.支持多態(tài)和依賴注入:在使用依賴注入框架(如 Spring)時(shí),可以方便地注入不同的 DAO 實(shí)現(xiàn)類。這使得代碼更具靈活性和可測(cè)試性。
例如,在測(cè)試時(shí)可以注入一個(gè)模擬的 DAO 實(shí)現(xiàn)類,而在生產(chǎn)環(huán)境中注入實(shí)際的數(shù)據(jù)庫(kù)操作的 DAO 實(shí)現(xiàn)類。
4.便于團(tuán)隊(duì)協(xié)作:不同的開(kāi)發(fā)者可以同時(shí)工作在接口的實(shí)現(xiàn)和使用接口的代碼上,提高開(kāi)發(fā)效率。
假設(shè)一個(gè)團(tuán)隊(duì)中,一部分人負(fù)責(zé)實(shí)現(xiàn) DAO 接口,另一部分人負(fù)責(zé)編寫(xiě)業(yè)務(wù)邏輯使用這些接口,兩者可以并行開(kāi)發(fā),互不干擾。
5.提高代碼的可擴(kuò)展性:當(dāng)需要添加新的功能或方法時(shí),只需在接口中添加,然后在具體的實(shí)現(xiàn)類中進(jìn)行實(shí)現(xiàn),而不會(huì)影響到現(xiàn)有的使用代碼。
比如,最初的接口只有查詢方法,后來(lái)需要添加插入和更新方法,只需在接口中添加,然后在實(shí)現(xiàn)類中實(shí)現(xiàn)即可。
案例優(yōu)化-Dao層:
接口:
package com.example.springbootwebpractice.dao;
import com.example.springbootwebpractice.pojo.Emp;
import java.util.List;
public interface EmpDao {
List<Emp> listEmp();
}實(shí)現(xiàn)類:
package com.example.springbootwebpractice.dao.impl;
import com.example.springbootwebpractice.dao.EmpDao;
import com.example.springbootwebpractice.pojo.Emp;
import com.example.springbootwebpractice.utils.XmlParserUtils;
import java.util.List;
public class EmpDaoA implements EmpDao {
@Override
public List<Emp> listEmp() {
String file=this.getClass().getClassLoader().getResource("emp.xml").getFile();
//System.out.println(file);
List<Emp> emplist= XmlParserUtils.parse(file,Emp.class);
return emplist;
}
}簡(jiǎn)單來(lái)說(shuō),就是先創(chuàng)建一個(gè)接口限制一下方法名和返回結(jié)果,具體是從數(shù)據(jù)庫(kù)還是文件取從具體繼承的類中實(shí)現(xiàn)
(2)業(yè)務(wù)邏輯層(service)
案例優(yōu)化-service層
接口:
package com.example.springbootwebpractice.service;
import com.example.springbootwebpractice.pojo.Emp;
import java.util.List;
public interface EmpService {
List<Emp> listEmp();
}具體實(shí)現(xiàn)類:
package com.example.springbootwebpractice.service.impl;
import com.example.springbootwebpractice.dao.EmpDao;
import com.example.springbootwebpractice.dao.impl.EmpDaoA;
import com.example.springbootwebpractice.pojo.Emp;
import com.example.springbootwebpractice.service.EmpService;
import java.util.Iterator;
import java.util.List;
public class EmpServiceA implements EmpService {
private EmpDao empDao=new EmpDaoA();
//面向接口定義對(duì)象
@Override
public List<Emp> listEmp() {
//1.調(diào)用Dao獲取數(shù)據(jù)
List<Emp> emplist=empDao.listEmp();
//2.數(shù)據(jù)處理
Iterator<Emp> it=emplist.iterator();
while (it.hasNext()){
String gender = it.next().getGender();
if(gender.equals("1")){
it.next().setGender("男");
}else if(gender.equals("2")){
it.next().setGender("女");
}
String job= it.next().getJob();
if(job.equals("1")){
it.next().setJob("講師");
}else if(job.equals("2")){
it.next().setJob("班主任");
}else if(job.equals("3")){
it.next().setJob("就業(yè)指導(dǎo)");
}
}
return emplist;
}
}(3)表現(xiàn)層
案例優(yōu)化-controller層
package com.example.springbootwebpractice.controller;
import com.example.springbootwebpractice.pojo.Emp;
import com.example.springbootwebpractice.pojo.Result;
import com.example.springbootwebpractice.service.EmpService;
import com.example.springbootwebpractice.service.impl.EmpServiceA;
import com.example.springbootwebpractice.utils.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Iterator;
import java.util.List;
@RestController
public class EmpController {
private EmpService empService=new EmpServiceA();
@RequestMapping("/listEmp")
public Result list(){
List<Emp> emplist=empService.listEmp();
//3.響應(yīng)數(shù)據(jù)
return Result.success(emplist);
}
}分層處理流程圖

之所以要分層,是為了在編碼時(shí)專注于某一件事情,維護(hù)起來(lái)更加簡(jiǎn)單
分層解耦
1.耦合和內(nèi)聚
耦合(Coupling)和內(nèi)聚(Cohesion) 是軟件工程中用于評(píng)估軟件模塊設(shè)計(jì)質(zhì)量的兩個(gè)重要概念。
耦合 指的是不同模塊之間相互依賴的程度。
耦合程度低意味著模塊之間的依賴關(guān)系較少、較弱,一個(gè)模塊的修改對(duì)其他模塊的影響較小。
例如,如果模塊 A 直接操作模塊 B 內(nèi)部的數(shù)據(jù),這就是強(qiáng)耦合。但如果模塊 A 只是通過(guò)模塊 B 提供的明確接口進(jìn)行交互,這就是弱耦合。
耦合的類型包括:
- 內(nèi)容耦合:一個(gè)模塊直接訪問(wèn)另一個(gè)模塊的內(nèi)部數(shù)據(jù)或代碼。這是最強(qiáng)且最不好的耦合形式。
- 公共耦合:多個(gè)模塊都訪問(wèn)同一個(gè)全局?jǐn)?shù)據(jù)環(huán)境。
- 控制耦合:一個(gè)模塊通過(guò)傳遞控制信息(如標(biāo)志、開(kāi)關(guān)量等)來(lái)影響另一個(gè)模塊的功能。
- 標(biāo)記耦合:兩個(gè)模塊之間通過(guò)參數(shù)傳遞復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。
- 數(shù)據(jù)耦合:模塊之間通過(guò)參數(shù)傳遞基本數(shù)據(jù)類型的數(shù)據(jù)。這是理想的、低耦合的方式。
內(nèi)聚 則衡量的是一個(gè)模塊內(nèi)部各個(gè)元素之間的關(guān)聯(lián)程度。
內(nèi)聚程度高表示模塊內(nèi)部的元素緊密相關(guān),共同完成一個(gè)明確、單一的功能。
例如,一個(gè)專門負(fù)責(zé)處理學(xué)生成績(jī)計(jì)算的模塊,如果它只包含與成績(jī)計(jì)算相關(guān)的代碼和數(shù)據(jù),就是高內(nèi)聚。
低內(nèi)聚的情況比如一個(gè)模塊既處理成績(jī)計(jì)算,又處理學(xué)生的課程安排,功能過(guò)于混雜。
在軟件設(shè)計(jì)中,我們通常希望達(dá)到低耦合高內(nèi)聚的目標(biāo)。
舉例來(lái)說(shuō),假設(shè)有一個(gè)學(xué)生管理系統(tǒng),其中有學(xué)生信息模塊、課程模塊和成績(jī)模塊。
- 如果學(xué)生信息模塊直接修改成績(jī)模塊的數(shù)據(jù),這就是強(qiáng)耦合,不利于系統(tǒng)的維護(hù)和擴(kuò)展。
- 而如果每個(gè)模塊都專注于自己明確的功能,如學(xué)生信息模塊只負(fù)責(zé)管理學(xué)生的基本信息,成績(jī)模塊只負(fù)責(zé)成績(jī)的相關(guān)操作,這就是高內(nèi)聚,使得每個(gè)模塊的功能清晰、單一,易于理解和維護(hù)。
低耦合和高內(nèi)聚的設(shè)計(jì)可以提高軟件的可維護(hù)性、可擴(kuò)展性和可重用性,降低軟件開(kāi)發(fā)和維護(hù)的成本。



控制反轉(zhuǎn) 是一種設(shè)計(jì)原則,它指的是控制權(quán)從應(yīng)用程序代碼轉(zhuǎn)移到了外部的框架或容器。在傳統(tǒng)的編程中,對(duì)象的創(chuàng)建和依賴關(guān)系的管理通常由應(yīng)用程序自身負(fù)責(zé)。而在控制反轉(zhuǎn)的理念下,這些控制權(quán)被交給了外部的機(jī)制。
依賴注入 是實(shí)現(xiàn)控制反轉(zhuǎn)的一種方式。它是指在一個(gè)對(duì)象創(chuàng)建或使用的時(shí)候,將其依賴的對(duì)象通過(guò)外部的方式(例如構(gòu)造函數(shù)、屬性設(shè)置方法等)傳遞給它,而不是由對(duì)象自身去創(chuàng)建或獲取這些依賴。
IOC&DI入門

@Component 注解用于將一個(gè)普通的 Java 類標(biāo)記為一個(gè) Spring 管理的組件。這意味著 Spring 容器會(huì)負(fù)責(zé)創(chuàng)建該類的實(shí)例(bean),并對(duì)其進(jìn)行管理(包括依賴注入等)。
@Autowired 是 Spring 框架中的一個(gè)注解,用于實(shí)現(xiàn)依賴自動(dòng)注入。
當(dāng)一個(gè)類中的成員變量、方法參數(shù)或構(gòu)造函數(shù)參數(shù)被標(biāo)注為 @Autowired 時(shí),Spring 框架會(huì)自動(dòng)查找匹配類型的 bean,并將其注入到該成員變量、方法參數(shù)或構(gòu)造函數(shù)參數(shù)中。
七.IOC詳解
respon除了 @Component 注解,通常還有一些具有更具體語(yǔ)義的衍生注解,如:
@Service:用于標(biāo)注服務(wù)層的組件。@Repository:用于標(biāo)注數(shù)據(jù)訪問(wèn)層(DAO 層)的組件。@Controller:用于標(biāo)注控制層(MVC 架構(gòu)中的控制器)的組件。
這些注解的功能與 @Component 類似,但更明確地表明了組件的類型和用途,有助于提高代碼的可讀性和可維護(hù)性。

@Controller也可以不用添加,前面講過(guò),@RestController注解里包含了這個(gè)注解
可以在注解后面加(“name”)用來(lái)給此bean對(duì)象署名
默認(rèn)類名首字母小寫(xiě)
組件掃描



八.DI詳解
如果多個(gè)繼承相同接口的bean對(duì)象都依賴注入的話,會(huì)發(fā)生矛盾
用以下方式,處理矛盾
@Primary注解
@Primary 是 Spring 框架中的注解。當(dāng)在多個(gè)相同類型的 bean 被定義時(shí),使用 @Primary 注解可以指定其中一個(gè)作為首要的(primary)bean。當(dāng) Spring 進(jìn)行依賴注入并且需要選擇一個(gè)該類型的 bean 時(shí),如果存在被標(biāo)注為 @Primary 的 bean,就會(huì)優(yōu)先選擇它。

@Qualifier注解
“Qualifier”常見(jiàn)中文釋義為“限定詞;合格者;修飾語(yǔ)”。
@Qualifier 是 Spring 框架中的注解。用于在存在多個(gè)相同類型的 bean 時(shí),更精確地指定要注入的 bean。
當(dāng)多個(gè) bean 屬于同一類型,而僅使用 @Autowired 無(wú)法明確要注入哪一個(gè)時(shí),可以結(jié)合 @Qualifier 注解,通過(guò)指定 bean 的名稱來(lái)明確注入的對(duì)象。

@Resource注解
@Resource是 Java 中用于依賴注入的注解。
它默認(rèn)按照名稱進(jìn)行依賴注入,如果找不到與名稱匹配的 bean,則按照類型進(jìn)行匹配注入。


到此這篇關(guān)于SpringBoot Web開(kāi)發(fā)(請(qǐng)求,響應(yīng),分層解耦)的文章就介紹到這了,更多相關(guān)SpringBoot Web請(qǐng)求響應(yīng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring 事件監(jiān)聽(tīng)機(jī)制實(shí)現(xiàn)跨模塊調(diào)用的思路詳解
之前一個(gè)項(xiàng)目,有兩個(gè)模塊,A 模塊需要依賴 B 模塊,但現(xiàn)在 B 模塊有地方需要調(diào)用 A 模塊的方法,如果直接依賴,又會(huì)產(chǎn)生循環(huán)依賴問(wèn)題,最終選擇使用 spring 的事件監(jiān)聽(tīng)來(lái)解決該問(wèn)題,下面給大家介紹Spring 事件監(jiān)聽(tīng)機(jī)制實(shí)現(xiàn)跨模塊調(diào)用的思路,感興趣的朋友一起看看吧2024-05-05
Springboot mybatis常見(jiàn)配置問(wèn)題解決
這篇文章主要介紹了Springboot mybatis常見(jiàn)配置問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
idea自帶database連接mysql失敗問(wèn)題的解決辦法
在IDEA?帶的數(shù)據(jù)庫(kù)連接?具中,可以連接MySQL數(shù)據(jù)庫(kù),但是有的時(shí)候連接出現(xiàn)錯(cuò)誤,連接不上數(shù)據(jù)庫(kù),下面這篇文章主要給大家介紹了關(guān)于idea自帶database連接mysql失敗問(wèn)題的解決辦法,需要的朋友可以參考下2023-06-06
SpringBoot實(shí)現(xiàn)導(dǎo)出復(fù)雜對(duì)象到Excel文件
這篇文章主要為大家詳細(xì)介紹了如何使用Hutool和EasyExcel兩種方式來(lái)實(shí)現(xiàn)在Spring Boot項(xiàng)目中導(dǎo)出復(fù)雜對(duì)象到Excel文件,需要的小伙伴可以參考下2025-03-03
以Spring Boot的方式顯示圖片或下載文件到瀏覽器的示例代碼
這篇文章主要介紹了以Spring Boot的方式顯示圖片或下載文件到瀏覽器的示例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
java工具類SendEmailUtil實(shí)現(xiàn)發(fā)送郵件
這篇文章主要為大家詳細(xì)介紹了java工具類SendEmailUtil實(shí)現(xiàn)發(fā)送郵件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02

