在前後端分離模式下,Spring MVC 的作用主要集中在處理後端的業務邏輯和 API 介面,而不再直接管理視圖部分。也就是說,Spring MVC 的重點是如何處理客戶端的請求並返回數據(通常以 JSON 或 XML 格式),而視圖渲染交給前端框架(如 Vue.js、React 等)來完成。 下麵 ...
在前後端分離模式下,Spring MVC 的作用主要集中在處理後端的業務邏輯和 API 介面,而不再直接管理視圖部分。也就是說,Spring MVC 的重點是如何處理客戶端的請求並返回數據(通常以 JSON 或 XML 格式),而視圖渲染交給前端框架(如 Vue.js、React 等)來完成。
下麵是針對前後端分離模式的 Spring MVC 詳細講解。
1、Spring MVC 工作流程
在前後端分離的模式下,Spring MVC 的工作流程仍然包含以下步驟,但其核心作用是處理客戶端發送的請求並返回數據,而不再渲染視圖。
- 客戶端發送請求:前端應用(如 Vue.js 或 React)通過 AJAX 或 Axios 向後端發送 HTTP 請求,通常是 RESTful API 請求。
- DispatcherServlet 攔截請求:
DispatcherServlet
作為 Spring MVC 的前端控制器,攔截所有的 HTTP 請求,並將請求轉發給合適的處理器(Controller)。 - 處理請求:Controller 根據請求路徑和請求參數,調用業務邏輯處理或資料庫操作,生成相應的響應數據。
- 返回 JSON 或 XML 數據:Controller 將處理結果作為 JSON 或 XML 格式的數據返回給客戶端。前端應用再根據返回的數據進行頁面更新或其他操作。
2、核心組件
在前後端分離的模式中,Spring MVC 的核心組件有以下幾個:
2.1 Controller(控制器)
控制器主要負責接收前端的請求,並返回數據。通常,控制器中的方法會通過 @RestController
註解來簡化開發,它表示這個控制器只會返回數據而不是視圖。
示例:
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
// 模擬從資料庫獲取用戶
User user = new User(id, "John", 25);
return user; // 返回 JSON 數據
}
@PostMapping("/create")
public User createUser(@RequestBody User user) {
// 保存用戶邏輯
return user; // 返回保存後的用戶信息
}
}
在這個例子中,UserController
通過 RESTful API 向前端返回用戶數據,而不是返回 HTML 頁面。
2.2 @RestController 註解
@RestController
是 @Controller
和 @ResponseBody
的組合註解,簡化了返回 JSON、XML 等格式的開發流程。每個方法返回的數據會自動序列化為客戶端期望的格式。
2.3 RequestMapping 註解
@RequestMapping
或簡化的 @GetMapping
、@PostMapping
用於定義介面的 URL 路徑,並指定 HTTP 方法類型(GET、POST、PUT、DELETE 等)。它們用來路由前端請求到具體的控制器方法。
2.4 @RequestBody 和 @ResponseBody
@RequestBody
:用於從請求體中提取 JSON 或 XML 格式的數據並映射到方法參數上。前端通常發送 JSON 格式的數據,Spring MVC 會自動將其轉換為 Java 對象。@ResponseBody
:用於將方法的返回值自動序列化為 JSON 或 XML 格式返回給客戶端。方法的返回值會直接作為 HTTP 響應體返回,而不是視圖名稱。
3、RequestMapping
@RequestMapping
是 Spring MVC 中的核心註解之一,用於將 HTTP 請求映射到處理器方法(通常是控制器類中的方法)。在前後端分離模式下,@RequestMapping
和相關的註解主要用於構建 RESTful API,確保前端能通過 HTTP 請求與後端進行交互。@RequestMapping
可以精確地映射 HTTP 請求的 URL、請求方法、請求參數等,以便後端能夠根據前端的請求路徑和請求類型執行相應的操作。
1. @RequestMapping
註解的基本使用
@RequestMapping
可以用於類和方法上,常見的組合是將它用在控制器類上以指定公共路徑,方法上用來指定更具體的路徑和請求操作。
類級別的 @RequestMapping
類級別的 @RequestMapping
用於定義公共的 URL 路徑首碼,所有在此類中的方法共用這個首碼。
示例:
@RestController
@RequestMapping("/api/users")
public class UserController {
// 所有路徑都會以 "/api/users" 為首碼
}
方法級別的 @RequestMapping
方法級別的 @RequestMapping
用於指定具體的請求路徑和操作,可以與類級別的路徑組合形成完整的 URL。
示例:
@RestController
@RequestMapping("/api/users")
public class UserController {
@RequestMapping("/list")
public List<User> getAllUsers() {
// 處理 "/api/users/list" 請求
return userService.getAllUsers();
}
@RequestMapping("/details/{id}")
public User getUserById(@PathVariable Long id) {
// 處理 "/api/users/details/{id}" 請求
return userService.getUserById(id);
}
}
2. @RequestMapping
的常用屬性
@RequestMapping
提供了多個屬性用於更加精確地匹配 HTTP 請求:
2.1 value
屬性
value
屬性指定請求的路徑,可以是一個字元串數組,表示允許多個 URL 訪問同一個處理器方法。
@RequestMapping(value = {"/list", "/all"})
public List<User> getAllUsers() {
// "/list" 或 "/all" 都會被映射到這個方法
return userService.getAllUsers();
}
2.2 method
屬性
method
屬性用於指定支持的 HTTP 請求方法,例如 GET
、POST
、PUT
、DELETE
等。
@RequestMapping(value = "/create", method = RequestMethod.POST)
public User createUser(@RequestBody User user) {
// 只處理 POST 請求
return userService.saveUser(user);
}
如果你希望更簡潔地定義請求方法,可以使用特定的註解替代 @RequestMapping
,如 @GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
。
2.3 params
屬性
params
屬性可以用於限制請求參數的存在與否。例如,如果你希望方法僅在某個特定參數存在時被調用,可以使用 params
屬性。
http://example.com/login?username=yourUsername&password=yourPassword
@RequestMapping(value = "/search", params = {"username", "password"})
public List<User> searchUsersByName(@RequestParam String username, @RequestParam String password) {
// 只有請求中包含 username 參數時,這個方法才會被執行
return userService.searchByName(username);
}
2.4 headers
屬性
headers
屬性用於根據 HTTP 請求頭匹配請求。這個屬性可以用來在某些特定的請求頭存在時執行某個處理方法。
@RequestMapping(value = "/info", headers = "Accept=application/json")
public User getUserInfo() {
// 僅在 Accept 頭為 "application/json" 時處理請求
return userService.getUserInfo();
}
2.5 consumes
屬性
consumes
屬性用於指定請求的 Content-Type
類型。例如,如果你希望處理的請求體是 JSON 格式,可以這樣指定:
@RequestMapping(value = "/create", method = RequestMethod.POST, consumes = "application/json")
public User createUser(@RequestBody User user) {
// 只接受 Content-Type 為 "application/json" 的請求
return userService.saveUser(user);
}
2.6 produces
屬性
produces
屬性用於指定返回的響應類型(Content-Type
)。例如,如果你希望返回 JSON 格式的響應,可以這樣定義:
@RequestMapping(value = "/user/{id}", produces = "application/json")
public User getUserById(@PathVariable Long id) {
// 返回 JSON 格式的數據
return userService.getUserById(id);
}
2.7 path
屬性
path
是 value
的別名,用於定義請求的 URL 路徑。如果需要更具語義化地定義路徑,可以使用 path
屬性。
@RequestMapping(path = "/details/{id}")
public User getUserDetails(@PathVariable Long id) {
return userService.getUserDetails(id);
}
3. 組合使用
@RequestMapping
可以組合使用多個屬性來實現更加複雜的請求匹配。例如:
@RequestMapping(
value = "/update",
method = RequestMethod.PUT,
consumes = "application/json",
produces = "application/json"
)
public User updateUser(@RequestBody User user) { // @RequestBody 註解用於將請求體中的 JSON 數據轉換為 User 對象。
// 只處理 PUT 請求,且請求體和響應體都是 JSON 格式
return userService.updateUser(user);
}
4. 常見註解簡化形式
為了簡化常見的 HTTP 操作,Spring 提供了幾個註解作為 @RequestMapping
的簡化形式:
@GetMapping
:用於處理 GET 請求@PostMapping
:用於處理 POST 請求@PutMapping
:用於處理 PUT 請求@DeleteMapping
:用於處理 DELETE 請求@PatchMapping
:用於處理 PATCH 請求
這些註解都相當於 @RequestMapping
的簡化版,直接指定了請求方法類型。
示例:
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
@PostMapping("/user/create")
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
4、路徑變數和請求參數
4.1 @PathVariable
@PathVariable
註解用於從 URL 的路徑中提取數據,並將這些數據綁定到控制器方法的參數上。它通常與 URL 模板中的占位符 {}
一起使用。
1. 基本用法
1.1 單一路徑變數
在控制器方法中,可以使用 @PathVariable
註解來提取單個路徑變數。
示例:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public String getUser(@PathVariable("id") String id) {
return "User ID: " + id;
}
}
- 請求 URL:
/users/123
id
變數將被設置為123
,結果:User ID: 123
1.2 多個路徑變數
可以在 URL 模板中使用多個路徑變數,併在方法中提取這些變數。
示例:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}/details/{detailId}")
public String getUserDetails(@PathVariable("id") String id,
@PathVariable("detailId") String detailId) {
return "User ID: " + id + ", Detail ID: " + detailId;
}
}
- 請求 URL:
/users/123/details/456
id
變數將被設置為123
,detailId
變數將被設置為456
,結果:User ID: 123, Detail ID: 456
2. URL 模板中的占位符
2.1 單層路徑變數
路徑變數用於 URL 模板中的單層路徑。
示例:
@GetMapping("/products/{productId}")
public String getProduct(@PathVariable("productId") String productId) {
return "Product ID: " + productId;
}
- URL:
/products/789
productId
變數將被設置為789
2.2 多層路徑變數
路徑變數可以用在 URL 的多層路徑中,提取不同層次的數據。
示例:
@GetMapping("/categories/{categoryId}/items/{itemId}")
public String getItem(@PathVariable("categoryId") String categoryId,
@PathVariable("itemId") String itemId) {
return "Category ID: " + categoryId + ", Item ID: " + itemId;
}
- URL:
/categories/1/items/42
categoryId
變數將被設置為1
,itemId
變數將被設置為42
3. 路徑變數的名稱匹配
@PathVariable
的名稱需要與 URL 模板中的占位符名稱匹配。如果不匹配,會導致綁定失敗。
示例:
@GetMapping("/items/{itemId}")
public String getItem(@PathVariable("itemId") String id) {
// 此處 id 是 itemId 的別名,實際名稱應與 URL 模板一致
return "Item ID: " + id;
}
4.2 @RequestParam
@RequestParam
是 Spring MVC 中用於處理 HTTP 請求參數的註解。它用於從 HTTP 請求的查詢字元串或表單參數中提取數據。它可以用於處理 GET
、POST
、PUT
、DELETE
等請求方法中的參數。
補充:
查詢字元串 是附加在 URL 末尾的鍵值對,用於向伺服器傳遞參數。它通常用於 GET
請求,也可以用於其他請求類型。查詢字元串以 ?
開始,後面跟隨參數鍵值對,用 &
分隔多個參數。
查詢字元串的格式為:
http://example.com/resource?key1=value1&key2=value2
表單參數 是通過 HTML 表單提交的鍵值對,通常在 POST
請求中使用。表單參數可以通過 application/x-www-form-urlencoded
或 multipart/form-data
編碼發送,具體取決於表單的內容類型。
表單數據的格式在 application/x-www-form-urlencoded
格式中,表單數據類似於查詢字元串:
key1=value1&key2=value2
示例
HTML 表單:
<form action="/register" method="post">
<input type="text" name="username" value="alice">
<input type="number" name="age" value="30">
<button type="submit">Register</button>
</form>
-
username
的值是alice
。 -
age
的值是30
。 -
表單提交的數據:
username=alice&age=30
-
返回結果:
Registered user: alice, Age: 30
總結
- 查詢字元串 是在 URL 中附加的參數,用於
GET
請求。 - 表單參數 是通過 HTML 表單提交的參數,通常用於
POST
請求。 - 在 Spring MVC 中,
@RequestParam
註解可以用於提取這兩種類型的請求參數。
1. 基本用法
1.1 從查詢字元串中獲取參數(GET 請求)
當使用 GET
請求時,參數通常附加在 URL 的查詢字元串中。
示例:
@RestController
public class UserController {
@GetMapping("/greet")
public String greet(@RequestParam("name") String name) {
return "Hello, " + name + "!";
}
}
- 請求 URL:
/greet?name=Alice
- 結果:
Hello, Alice!
1.2 從表單數據中獲取參數(POST 請求)
當使用 POST
請求時,參數通常是表單數據的一部分。
示例:
@RestController
public class UserController {
@PostMapping("/register")
public String register(@RequestParam("username") String username,
@RequestParam("password") String password) {
return "Registered user: " + username;
}
}
- 表單數據:
<form action="/register" method="post"> <input type="text" name="username"> <input type="password" name="password"> <button type="submit">Register</button> </form>
2. @RequestParam
的屬性
2.1 value
屬性
value
(或 name
)屬性指定請求參數的名稱。
示例:
@RequestParam(value = "name") String name
// 或
@RequestParam("name") String name
2.2 required
屬性
required
屬性用於指示參數是否是必需的。預設為 true
,如果請求中沒有提供該參數,Spring 將拋出異常。如果設置為 false
,則可以省略該參數。
示例:
@RequestParam(name = "name", required = false) String name
- 如果請求中不包含
name
參數,name
將被設置為null
,而不是拋出異常。
2.3 defaultValue
屬性
defaultValue
屬性用於指定當請求參數缺失時的預設值。如果參數缺失,則使用預設值而不是拋出異常。
示例:
@RequestParam(name = "name", defaultValue = "Guest") String name
- 如果請求中不包含
name
參數,name
將被設置為"Guest"
。
3. 路徑變數與請求參數的區別
- 路徑變數(Path Variables):用於從 URL 的路徑中提取數據。例如:
/users/{id}
。 - 請求參數(Request Parameters):用於從查詢字元串或表單數據中提取數據。例如:
/users?id=1
。
示例:
@RestController
public class UserController {
@GetMapping("/users/{id}")
public String getUser(@PathVariable("id") String id) {
return "User ID: " + id;
}
@GetMapping("/search")
public String search(@RequestParam("query") String query) {
return "Search query: " + query;
}
}
/users/1
使用路徑變數,id
是路徑的一部分。/search?query=spring
使用請求參數,query
是查詢字元串的一部分。
4. 組合使用
@RequestParam
可以與其他註解(如 @PathVariable
、@RequestBody
)組合使用,處理複雜的請求。
示例:
@RestController
public class UserController {
@PostMapping("/users/{id}")
public String updateUser(@PathVariable("id") String id,
@RequestParam("name") String name,
@RequestParam(name = "email", required = false) String email) {
return "Updated user ID: " + id + ", Name: " + name + ", Email: " + (email != null ? email : "Not provided");
}
}
5. 支持的請求內容類型
application/x-www-form-urlencoded
:傳統的表單提交方式。multipart/form-data
:用於文件上傳等複雜表單數據。application/json
:通常與@RequestBody
結合使用,但在特定情況下也可以用於@RequestParam
。
5、RESTful 風格
RESTful 風格一種基於資源和 HTTP 方法的 Web 服務設計方式,利用標準的 HTTP 動詞來處理資源操作,通過 JSON 等格式表示資源狀態。它具有可讀性高、靈活性強、標準化等優點,是現代 Web 開發中廣泛採用的 API 設計模式。
REST 代表 REpresentational State Transfer,意思是通過表現層狀態轉換進行系統交互。RESTful API 是基於 REST 原則設計的應用程式介面(API),常用於構建面向資源的網路服務。
1. RESTful 風格的核心概念
RESTful 風格主要圍繞以下幾個核心概念展開:
1.1 資源(Resource)
資源是 REST 的核心,表示網路上的數據或服務。每一個資源都通過一個唯一的 URI(Uniform Resource Identifier,統一資源標識符)來標識。例如:
http://example.com/users
:表示用戶集合資源。http://example.com/users/1
:表示具體用戶資源,用戶 ID 為 1。
資源通常以 名詞 來命名,並且在 URI 中儘量避免使用動詞。
1.2 HTTP 方法
RESTful API 使用標準的 HTTP 方法來對資源執行不同的操作,每個方法都與一個特定的動作語義相關:
-
GET:從伺服器獲取資源。
- 示例:
GET /users
獲取所有用戶,GET /users/1
獲取 ID 為 1 的用戶。
- 示例:
-
POST:向伺服器創建一個新的資源。
- 示例:
POST /users
在用戶集合中創建一個新用戶。
- 示例:
-
PUT:更新伺服器上的資源(通常是替換整個資源)。
- 示例:
PUT /users/1
更新 ID 為 1 的用戶信息。
- 示例:
-
PATCH:部分更新伺服器上的資源(通常是更新資源的某些欄位)。
- 示例:
PATCH /users/1
更新 ID 為 1 的用戶的部分信息。
- 示例:
-
DELETE:從伺服器上刪除資源。
- 示例:
DELETE /users/1
刪除 ID 為 1 的用戶。
- 示例:
1.3 狀態表示(Representation)
當客戶端請求一個資源時,伺服器將該資源的當前狀態通過 狀態表示 發送給客戶端。通常,RESTful API 使用 JSON 或 XML 來表示資源的狀態。例如,獲取用戶資源可能會返回這樣的 JSON 數據:
{
"id": 1,
"name": "Alice",
"email": "[email protected]"
}
1.4 無狀態性(Stateless)
RESTful 服務是 無狀態的,這意味著每個請求都應該包含所有必要的信息,伺服器不會在不同請求之間保留任何客戶端的狀態。每次請求都是獨立的,伺服器不會依賴之前請求中的信息。
1.5 URI 的結構化
RESTful API 的 URI 通常是結構化的,以層級關係表示資源。例如:
/users
:表示所有用戶。/users/1
:表示 ID 為 1 的用戶。/users/1/orders
:表示 ID 為 1 的用戶的訂單。
2. RESTful 風格的設計原則
RESTful API 設計遵循以下幾個原則:
2.1 資源的標識通過 URI
每個資源都應該有唯一的 URI,並且通過這個 URI 可以對資源進行操作。例如,用戶資源 /users
的 URI 是 /users
,單個用戶資源 /users/1
的 URI 是 /users/1
。
2.2 使用 HTTP 方法明確資源操作
HTTP 方法(GET、POST、PUT、DELETE 等)應該明確表示對資源的操作,避免在 URI 中使用動詞。例如,不推薦使用 /getUser
或 /deleteUser
這樣的 URI,應該使用 /users
並結合 HTTP 方法來實現。
2.3 使用 JSON 或 XML 表示資源
資源的狀態表示通常使用標準的格式,比如 JSON 或 XML,其中 JSON 更常用,因為它簡潔、易於解析。響應體中包含資源的狀態信息,並返回給客戶端。
2.4 無狀態的請求
每次客戶端與伺服器之間的交互都是獨立的,伺服器不保留客戶端的狀態。請求中的所有必要信息(如認證信息)都應該包含在每次請求中。
2.5 遵循標準的狀態碼
RESTful API 應該使用標準的 HTTP 狀態碼來反映操作的結果:
- 200 OK:請求成功並返回數據。
- 201 Created:成功創建資源。
- 204 No Content:操作成功但不返回數據(如 DELETE 請求)。
- 400 Bad Request:客戶端發送的請求無效(如參數錯誤)。
- 401 Unauthorized:未經授權的請求。
- 404 Not Found:請求的資源不存在。
- 500 Internal Server Error:伺服器內部錯誤。
3. RESTful 風格的示例
假設我們設計一個管理用戶的 RESTful API,以下是一些操作和對應的 RESTful URL 和 HTTP 方法:
操作 | HTTP 方法 | URL | 請求體 | 響應體 |
---|---|---|---|---|
獲取所有用戶 | GET | /users | 無 | 用戶列表 (JSON) |
獲取特定用戶(ID = 1) | GET | /users/1 | 無 | 用戶信息 (JSON) |
創建新用戶 | POST | /users | 用戶信息 (JSON) | 新建用戶信息 |
更新特定用戶(ID = 1) | PUT | /users/1 | 更新後的用戶信息 | 更新後的用戶信息 |
刪除特定用戶(ID = 1) | DELETE | /users/1 | 無 | 無 |
4. RESTful 與傳統 API 的對比
- RESTful API:基於資源和 HTTP 方法,簡潔、標準化、易於理解和擴展。請求和響應體通常使用 JSON,客戶端和伺服器之間的通信是無狀態的。
- 傳統 API:有時會在 URL 中包含動詞,例如
/getUser
、/deleteUser
,並且可能不遵循 HTTP 方法的語義。
5. RESTful API 的優勢
- 可讀性強:基於資源的設計和標準的 HTTP 方法,使得 API 直觀且易於理解。
- 靈活性高:RESTful API 可以支持多種客戶端(如 Web、移動設備)。
- 無狀態性:減少伺服器的負擔,因為它不需要保存客戶端的狀態。
- 標準化:使用標準的 HTTP 協議,容易被各種工具和平臺集成。
6. RESTful API 的局限
- 無狀態性限制:無狀態性雖然簡化了請求,但在一些需要持續狀態的操作(如登錄狀態)中需要額外設計。
- 複雜查詢場景:對於複雜的查詢場景,RESTful API 可能需要設計額外的過濾機制,如分頁、排序、搜索等。
6、數據返回格式
6.1 Json
在前後端分離的模式下,數據的返回格式通常是 JSON(預設),Spring MVC 使用 Jackson 或 Gson 等庫來自動將 Java 對象轉換為 JSON 格式。
示例:
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return new User(id, "Alice", 30); // 自動返回 JSON 數據
}
返回的 JSON 數據格式如下:
{
"id": 1,
"name": "Alice",
"age": 30
}
maven 官網 jackson 依賴
6.2 RequestEntity
RequestEntity
是 Spring 中用來封裝 HTTP 請求信息的類,它繼承自 HttpEntity
,並擴展了對 HTTP 請求頭、HTTP 方法(如 GET、POST)、URL 等的支持。RequestEntity
可以用來在處理請求時,獲取完整的請求信息,例如請求頭、請求體、請求方法等。
核心屬性
- HTTP 請求頭 (Headers):可以通過
RequestEntity
獲取請求中的 HTTP 頭信息。 - HTTP 請求方法 (Method):可以通過
RequestEntity
知曉請求是 GET、POST、PUT、DELETE 等方法。 - 請求 URL:
RequestEntity
包含完整的請求 URL,便於在處理請求時獲取 URL 參數。 - 請求體 (Body):對於 POST、PUT 等帶有請求體的方法,可以通過
RequestEntity
獲取請求體中的數據。
RequestEntity 常用方法
getMethod()
:獲取 HTTP 請求方法。getHeaders()
:獲取請求頭。getBody()
:獲取請求體。getUrl()
:獲取請求的 URL。
RequestEntity 示例
假設我們需要創建一個處理 POST 請求的介面,可以使用 RequestEntity
獲取請求的所有細節,包括請求頭、請求體和請求方法。
示例代碼:
@PostMapping("/process")
public ResponseEntity<String> handleRequestEntity(RequestEntity<String> requestEntity) {
// 獲取請求方法
HttpMethod method = requestEntity.getMethod();
// 獲取請求頭
HttpHeaders headers = requestEntity.getHeaders();
// 獲取請求體
String body = requestEntity.getBody();
// 列印請求信息
System.out.println("Method: " + method);
System.out.println("Headers: " + headers);
System.out.println("Body: " + body);
return ResponseEntity.ok("Request processed successfully");
}
請求示例:
curl -X POST http://localhost:8080/process -H "Content-Type: text/plain" -d "Hello, Spring!"
輸出:
Method: POST
Headers: [Content-Type:"text/plain"]
Body: Hello, Spring!
在這個例子中,RequestEntity
提供了對請求的詳細信息,便於處理複雜的請求邏輯。
構造 RequestEntity
RequestEntity
也可以通過靜態工廠方法來創建,例如:
RequestEntity<String> requestEntity = RequestEntity
.post(new URI("http://localhost:8080/process"))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body("{\"key\": \"value\"}");
這裡我們通過 RequestEntity
構造了一個 POST 請求,設置了請求頭和請求體。
RequestEntity 的適用場景
- 需要訪問 HTTP 請求的完整信息:當你需要獲取請求的所有細節(包括方法、頭、URL、體)時,
RequestEntity
非常有用。 - 複雜的 API 請求處理:在處理 REST API 請求時,它提供了獲取和操作請求細節的能力。
6.3 ResponseEntity
ResponseEntity
是 Spring 中用來封裝 HTTP 響應信息的類,它同樣繼承自 HttpEntity
,擴展了對 HTTP 響應頭、響應狀態碼的支持。ResponseEntity
允許我們更靈活地設置返回給客戶端的響應,包括狀態碼、響應體、響應頭等。
核心屬性
- HTTP 響應頭 (Headers):可以通過
ResponseEntity
設置或獲取返回給客戶端的 HTTP 頭信息。 - HTTP 響應體 (Body):可以通過
ResponseEntity
設置返回給客戶端的響應數據。 - HTTP 狀態碼 (Status Code):可以通過
ResponseEntity
設置 HTTP 響應的狀態碼(如200 OK
,404 Not Found
,500 Internal Server Error
等)。
ResponseEntity 常用方法
status(HttpStatus)
:設置響應狀態碼。body(Object)
:設置響應體內容。header(String, String)
:設置響應頭。ok()
:返回狀態碼 200 的響應。notFound()
:返回狀態碼 404 的響應。badRequest()
:返回狀態碼 400 的響應。
ResponseEntity 示例
假設我們需要處理一個文件上傳操作,並返回不同的響應狀態碼來表示上傳的結果:
示例代碼:
@PostMapping("/upload")
public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("文件為空");
}
try {
// 假設保存文件到指定路徑
String uploadDir = "/path/to/upload/directory/";
file.transferTo(new File(uploadDir + file.getOriginalFilename()));
return ResponseEntity.ok("文件上傳成功");
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件上傳失敗");
}
}
請求示例:
curl -X POST -F "file=@/path/to/file.txt" http://localhost:8080/upload
可能的響應:
- 成功時:
200 OK
,響應體為 "文件上傳成功" - 文件為空時:
400 Bad Request
,響應體為 "文件為空" - 出現伺服器錯誤時:
500 Internal Server Error
,響應體為 "文件上傳失敗"
在這個例子中,ResponseEntity
用來靈活地返回不同的狀態碼和消息。
構造 ResponseEntity
你可以通過靜態工廠方法來構建 ResponseEntity
,例如:
ResponseEntity<String> response = ResponseEntity
.status(HttpStatus.CREATED)
.header(HttpHeaders.LOCATION, "/new-resource")
.body("資源創建成功");
這個例子返回了一個 201 Created 的響應,同時設置了 Location
響應頭,指向新創建的資源路徑。
ResponseEntity 的適用場景
- 需要靈活設置響應:當你需要自定義響應的狀態碼、頭、體時,
ResponseEntity
提供了很好的靈活性。 - RESTful API 響應:在 REST API 開發中,它是一個常見的選擇,便於構建符合 HTTP 標準的響應。
6.4 RequestEntity
和 ResponseEntity
的關係
RequestEntity
主要用於處理 HTTP 請求,幫助我們獲取請求的所有詳細信息;而 ResponseEntity
則用於構建和返回 HTTP 響應,幫助我們返回自定義的響應數據、頭和狀態碼。
常見組合使用場景
在 Spring MVC 的控制器方法中,你可以同時使用 RequestEntity
來獲取請求信息,使用 ResponseEntity
返迴響應。例如:
@PostMapping("/process-data")
public ResponseEntity<String> handleRequest(RequestEntity<String> requestEntity) {
// 獲取請求體
String requestBody = requestEntity.getBody();
// 處理邏輯...
// 返迴響應
return ResponseEntity.ok("處理成功:" + requestBody);
}
7、文件的上傳和下載
在 Spring MVC 中,文件上傳和下載是常見的需求,通常通過 MultipartFile
和 HttpServletResponse
等組件來處理。下麵我們來詳細講解文件上傳和下載的實現。
1. 文件上傳
1.1 使用 MultipartFile
實現文件上傳
Spring MVC 提供了 MultipartFile
介面來處理文件上傳。為了支持文件上傳,首先需要在項目的配置文件中啟用對多部分請求的支持。
1.2 配置 Spring Boot 以支持文件上傳
在 application.properties
或 application.yml
中配置最大文件上傳大小:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB
max-file-size
:限制單個文件的大小。max-request-size
:限制整個請求的大小。
1.3 文件上傳的 Controller 示例
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import java.io.File;
import java.io.IOException;
@RestController
@RequestMapping("/files")
public class FileUploadController {
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
// 檢查文件是否為空
if (file.isEmpty()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("文件為空");
}
// 獲取文件名
String fileName = file.getOriginalFilename();
try {
// 文件保存路徑(可以根據需求修改)
String uploadDir = "/path/to/upload/directory/";
File dest = new File(uploadDir + fileName);
// 保存文件到伺服器
file.transferTo(dest);
return ResponseEntity.ok("文件上傳成功:" + fileName);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件上傳失敗");
}
}
}
1.4 前端文件上傳表單示例
<form method="POST" enctype="multipart/form-data" action="/files/upload">
<input type="file" name="file"/>
<button type="submit">上傳文件</button>
</form>
在這個例子中,文件上傳表單使用 enctype="multipart/form-data"
進行編碼,使文件能被正確地傳輸到伺服器。
2. 文件下載
文件下載通常通過 HttpServletResponse
來設置響應頭並將文件的內容寫入到輸出流中。
2.1 文件下載的 Controller 示例
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
@RestController
@RequestMapping("/files")
public class FileDownloadController {
@GetMapping("/download/{fileName}")
public void downloadFile(@PathVariable("fileName") String fileName, HttpServletResponse response) {
// 文件存儲路徑
String filePath = "/path/to/upload/directory/" + fileName;
File file = new File(filePath);
if (!file.exists()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 設置響應頭
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setContentLengthLong(file.length());
try (FileInputStream fis = new FileInputStream(file);
OutputStream os = response.getOutputStream()) {
// 將文件數據寫入輸出流
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
} catch (IOException e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}
2.2 前端文件下載鏈接示例
<a href="/files/download/sample.txt">下載文件</a>
在這個示例中,當用戶點擊鏈接時,瀏覽器會發出下載請求,並觸發 downloadFile
方法。該方法會將伺服器上的文件內容以流的形式發送到客戶端。
3. 文件上傳與下載的詳細解釋
3.1 文件上傳
MultipartFile
:Spring MVC 提供的介面,用於處理上傳的文件。你可以通過@RequestParam("file")
獲取上傳的文件。transferTo
:MultipartFile
提供的一個方法,用於將上傳的文件保存到指定路徑。- 表單的
enctype
:文件上傳表單的enctype="multipart/form-data"
,它允許文件和其他數據一起被髮送。
3.2 文件下載
HttpServletResponse
:用於生成 HTTP 響應,包括設置響應頭和寫入響應體。Content-Type
:指定響應的內容類型為application/octet-stream
,表示該響應是一個二進位流。Content-Disposition
:用於指示瀏覽器將響應內容作為附件下載,而不是直接顯示。例如,attachment; filename="file.txt"
會強制瀏覽器下載文件,並將其命名為file.txt
。- 流操作:通過
FileInputStream
讀取文件內容,並通過OutputStream
將內容寫入響應體中。
4. 註意事項
- 文件路徑安全性:在處理文件路徑時,確保不要暴露伺服器文件系統的敏感信息。建議使用文件存儲服務或資料庫來管理上傳的文件路徑。
- 文件上傳大小限制:在實際生產環境中,除了在 Spring 配置文件中設置文件上傳大小限制之外,也可以通過 Web 伺服器(如 Tomcat、Nginx)進行限制。
- 異常處理:在文件上傳和下載過程中,可能會出現各種異常(如文件不存在、許可權問題等),應對這些異常進行適當的處理。
5. 總結
Spring MVC 提供了簡潔的方式來處理文件上傳和下載。通過使用 MultipartFile
和 HttpServletResponse
,開發者可以輕鬆實現文件上傳和下載的功能。
8、攔截器
在 Spring MVC 中,攔截器(Interceptor
)是一個用於在請求處理的不同階段進行攔截和處理的機制。攔截器可以攔截進入控制器之前、控制器執行之後以及請求完成之後的操作,並允許開發者在這些過程中自定義邏輯。攔截器的使用使得我們可以實現諸如許可權驗證、日誌記錄、數據預處理、響應數據後處理等功能。
攔截器類似於過濾器(Filter
),但與過濾器不同的是,攔截器專註於與 Spring MVC 的請求處理流程結合得更緊密,並且能夠直接訪問 Spring 的上下文和數據。
1. 攔截器的工作原理
攔截器通過實現 HandlerInterceptor
介面來實現,它定義了三個主要的回調方法,分別對應請求處理的不同階段:
三個主要方法:
-
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- 時機:在請求到達控制器之前執行(即在執行控制器方法之前)。
- 作用:用於處理一些前置邏輯,如認證、許可權檢查、日誌記錄等。
- 返回值:返回
true
時繼續執行下一個攔截器或進入控制器方法,返回false
則中斷請求處理,不會繼續執行後續攔截器或控制器。
-
postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
- 時機:在控制器方法執行之後,視圖渲染之前執行。
- 作用:可以修改控制器返回的
ModelAndView
,對數據進行二次處理,或者決定渲染的視圖。
-
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
- 時機:在整個請求完成之後(視圖渲染完成後)執行。
- 作用:用於進行一些資源清理、日誌記錄、異常處理等工作。
2. 如何實現攔截器
步驟一:創建攔截器類
攔截器類需要實現 HandlerInterceptor
介面。
示例攔截器實現
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class MyInterceptor implements HandlerInterceptor {
// 進入控制器之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle: 請求前檢查許可權...");
// 可以進行許可權驗證邏輯,返回 false 則攔截請求
String authToken = request.getHeader("Authorization");
if (authToken == null || !authToken.equals("VALID_TOKEN")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false; // 攔截請求
}
return true; // 繼續處理請求
}
// 控制器執行後,視圖渲染前
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle: 可以修改 ModelAndView...");
}
// 請求結束,視圖渲染完成後
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion: 資源清理...");
}
}
步驟二:註冊攔截器
創建好攔截器類後,我們需要將其註冊到 Spring 的攔截器鏈中。通常,我們通過配置類來註冊攔截器。
示例配置
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 註冊攔截器
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**") // 攔截所有路徑
.excludePathPatterns("/login", "/public/**"); // 排除某些路徑
}
}
在這個配置中,攔截器 MyInterceptor
被註冊到攔截器鏈中,並指定攔截所有請求路徑(/**
),同時排除 /login
和 /public/**
的路徑不被攔截。
3. 攔截器與過濾器的區別
相同點:
- 兩者都可以在請求到達控制器之前和請求完成之後執行邏輯。
- 都可以用於執行諸如許可權驗證、日誌記錄、性能監控等功能。
不同點:
攔截器 (Interceptor) | 過濾器 (Filter) |
---|---|
作用於 Spring MVC 的請求處理流程,與 Spring 的上下文結合緊密。 | 作用於整個 web 應用的請求鏈,與 Spring 無關。 |
可以獲取控制器信息,並且可以操作 ModelAndView 。 |
只能操作原生的 HttpServletRequest 和 HttpServletResponse ,不涉及 Spring MVC 的特性。 |
實現 HandlerInterceptor 介面。 |
實現 javax.servlet.Filter 介面。 |
常用於 RESTful API 中的數據預處理、後處理。 | 常用於全局的過濾、編碼處理、安全檢查等。 |
4. 攔截器的常見應用場景
1. 許可權驗證
通過 preHandle
方法在請求進入控制器之前攔截請求,進行許可權驗證或認證處理。如果驗證失敗,可以返回相應的錯誤響應,終止請求繼續執行。
示例:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String authToken = request.getHeader("Authorization");
if (authToken == null || !isValidToken(authToken)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return false;
}
return true;
}
2. 日誌記錄
在 preHandle
或 afterCompletion
中記錄請求的日誌,包括請求的 URL、參數、處理時間等。
示例:
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long startTime = (Long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
System.out.println("Request URL: " + request.getRequestURL() + " - Time Taken: " + (endTime - startTime) + "ms");
}
3. 數據預處理
在 preHandle
中進行數據的預處理,例如對請求參數進行格式化、數據補充等。
示例:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String param = request.getParameter("date");
if (param != null) {
// 預處理日期參數
LocalDate date = LocalDate.parse(param, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
request.setAttribute("formattedDate", date);
}
return true;
}
4. 數據後處理
在 postHandle
中對控制器返回的 ModelAndView
進行修改,添加公共的頁面元素或數據。
示例:
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (modelAndView != null) {
modelAndView.addObject("footerMessage", "版權所有 © 2024");
}
}
5. 攔截器執行順序
如果有多個攔截器時,它們的執行順序是根據註冊時的順序來決定的。所有 preHandle
方法按照順序依次執行,所有 postHandle
和 afterCompletion
方法按照相反的順序執行。
假設有兩個攔截器 Interceptor1
和 Interceptor2
,它們的執行順序如下:
Interceptor1.preHandle()
->Interceptor2.preHandle()
-> 控制器方法 ->Interceptor2.postHandle()
->Interceptor1.postHandle()
->Interceptor2.afterCompletion()
->Interceptor1.afterCompletion()
6. 總結
HandlerInterceptor
是 Spring MVC 中的攔截器介面,它通過preHandle
、postHandle
和afterCompletion
三個方法來攔截請求的不同階段。- 攔截器通常用於許可權驗證、日誌記錄、數據預處理和後處理等場景。
- 與過濾器相比,攔截器與 Spring MVC 緊密集成,能夠獲取
9、跨域支持(CORS)
前後端分離時,前端和後端通常部署在不同的伺服器上,這會導致跨域問題。Spring MVC 提供了多種跨域配置方式,最常見的是使用 @CrossOrigin
註解。
示例:
@RestController
@CrossOrigin(origins = "http://localhost:8080")
@RequestMapping("/api/users")
public class UserController {
// 控制器方法
}
或者可以在全局配置跨域:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods("GET", "POST", "PUT", "DELETE");
}
}
8、常見開發模式
在前後端分離中,Spring MVC 通常與 RESTful API 模式結合使用,前端通過 HTTP 方法調用後端的 API 介面。後端僅負責數據處理和業務邏輯,而前端負責用戶界面、數據展示和交互。
-
前端使用 Axios 或 Fetch 調用後端 API:
前端通過 AJAX 向後端發送請求:axios.get('/api/users/1') .then(response => { console.log(response.data); }) .catch(error => { console.error(error); });
-
後端返回 JSON 數據:
後端 Controller 處理請求並返回 JSON 數據,前端接收數據後進行頁面更新。
9、總結
在前後端分離模式下,Spring MVC 的主要職責是處理請求、進行業務邏輯處理、返回 JSON 或 XML 格式的數據。其核心組件如 @RestController
、@RequestMapping
、@ResponseBody
等,簡化了 RESTful API 的開發流程。此外,Spring MVC 還提供了強大的異常處理機制和跨域支持,使前後端分離的開發更加順暢。