在 Day19 、 Day20 和 Day21 三天單元中,我們學會了如何從 URL (Uniform Resource Locator) 的路徑 (@PathVariable
) 和查詢參數 (@RequestParam
) 中獲取資訊,也掌握了如何處理請求主體 (Request Body) 中的 JSON 資料 (@RequestBody
)。
今天,我們要更上一層樓,探索 HTTP 請求 (Request) 中那些隱藏在幕後的重要資訊。這些資訊就像是每份請求的「元資料 (Metadata)」,它們雖然不直接出現在網址上,卻攜帶著客戶端的關鍵訊息。學會讀取它們,將讓你的應用程式變得更加智慧與安全。
學習目標:擴充知識,了解除了 URL 和 Body,還能從請求標頭 (Header) 和 Cookie 中獲取客戶端的請求資訊。
@RequestHeader
- 讀取請求的「數位護照」當瀏覽器或手機 App (客戶端) 向我們的伺服器發送請求時,除了主要內容外,還會附上一份「數位護照」,這就是 HTTP 標頭 (HTTP Header)。它記錄了客戶端的身份資訊、偏好設定以及請求的附加說明。
常見的標頭欄位包含:
User-Agent
:客戶端的類型(例如:Chrome 瀏覽器、iPhone App)。Accept-Language
:客戶端偏好的語言(例如:zh-TW, en-US)。Authorization
:用於身份驗證的令牌 (Token),例如 API 金鑰或 JWT。在 Spring Boot 中,@RequestHeader
這個註解 (Annotation) 的任務就是:幫助我們輕鬆讀取這些標頭欄位的值,並將它們綁定 (Bind) 到控制器 (Controller) 方法的參數 (Parameter) 上。
@RequestHeader
?假設我們的 API 有版本控制,並要求客戶端必須在標頭中提供 API-Version
。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HeaderController {
@GetMapping("/api/version")
public String getApiVersion(@RequestHeader("API-Version") String apiVersion) {
return "您正在使用的 API 版本是: " + apiVersion;
}
}
@RequestHeader("API-Version")
告訴 Spring:「請從請求標頭中找出 API-Version
欄位,並將其值賦予給 apiVersion
參數。」@RequestHeader
預設是必要的。如果客戶端發送請求時未包含 API-Version
標頭,Spring 會回傳一個 400 Bad Request
錯誤。有時標頭是可選的,如果客戶端沒提供,我們希望能有個預設行為。
@GetMapping("/greeting")
public String getGreeting(
@RequestHeader(name = "Accept-Language", defaultValue = "zh-TW") String language
) {
if (language.startsWith("en")) {
return "Hello, Guest!";
}
return "哈囉,訪客!";
}
defaultValue = "zh-TW"
:如果請求中沒有 Accept-Language
標頭,language
參數的值將會是 zh-TW
。當處理像 Authorization
這樣的認證標頭時,我們通常不希望它不存在時就直接報錯,而是要進行邏輯判斷。這時可以使用 required = false
。
import java.util.Optional;
@GetMapping("/user/profile")
public String getUserProfile(
@RequestHeader(name = "Authorization", required = false) Optional<String> authToken
) {
// 使用 Java 8 的 Optional<T> 是處理可選值的現代且安全的作法
return authToken
.map(token -> "Token 驗證成功!Token: " + token)
.orElse("訪客您好,請提供 Authorization Token 以登入。");
}
required = false
:告訴 Spring 這個標頭是可選的。Optional<String>
:這是一種優雅的寫法,可以避免惱人的空指針異常 (NullPointerException),清晰地表達了「這個值可能存在,也可能不存在」的意圖。@CookieValue
- 取得客戶端的「專屬識別證」Cookie 是網站儲存在使用者瀏覽器中的一小段資料,常用來記錄使用者的登入狀態、佈景主題偏好、購物車內容等,就像一張「專屬識別證」。
@CookieValue
的作用非常專一:將 HTTP 請求中特定名稱的 Cookie 值,綁定到方法的參數上。
@CookieValue
?讓我們來實作一個設定並讀取網站主題的功能。
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
@RestController
public class ThemeController {
// 1. 設定主題並存入 Cookie
@GetMapping("/set-theme")
public String setTheme(@RequestParam String theme, HttpServletResponse response) {
// 建立一個新的 Cookie
Cookie themeCookie = new Cookie("user-theme", theme);
themeCookie.setPath("/"); // 讓整個網站都可存取此 Cookie
themeCookie.setMaxAge(60 * 60 * 24 * 30); // 設定生命週期為 30 天
// 將 Cookie 加入到 HTTP 回應 (Response) 中,瀏覽器收到後會儲存起來
response.addCookie(themeCookie);
return "您的主題已設定為:" + theme;
}
// 2. 讀取 Cookie 中的主題
@GetMapping("/my-theme")
public String getTheme(
@CookieValue(name = "user-theme", defaultValue = "light") String userTheme
) {
return "您目前的主題是:" + userTheme;
}
}
http://localhost:8080/set-theme?theme=dark
來設定 Cookie。http://localhost:8080/my-theme
,你會看到 @CookieValue
成功讀取到了 dark
這個值。defaultValue = "light"
:我們也為首次來訪、還沒有 Cookie 的使用者提供了預設的 light
主題,避免了程式因找不到 Cookie 而報錯。@ModelAttribute
- 傳統網頁表單的資料裝訂工現在,讓我們來談談一個在傳統 Spring MVC (Model-View-Controller) 開發模式中非常重要的註解 (Annotation)。
想像一下使用者註冊時需要填寫一個包含許多欄位的 HTML 表單。如果用 @RequestParam
一個個去接收,程式碼會非常冗長。@ModelAttribute
就是為了解決這個問題而生,它可以自動將前端表單提交的參數,綁定到一個 Java 物件上。
@ModelAttribute
vs. @RequestBody
的關鍵區別這是初學者最容易混淆的觀念,但也是區分傳統 MVC 與現代 API 的關鍵。它們的核心區別在於資料來源與使用情境:
特性 | @ModelAttribute |
@RequestBody |
---|---|---|
資料來源 | 來自請求參數 (Parameters) | 來自請求主體 (Body) |
內容格式 (Content-Type) | application/x-www-form-urlencoded 或 multipart/form-data |
application/json , application/xml 等 |
比喻 | 像填寫一份紙本問卷,一欄一欄各自獨立 | 像寄送一個密封的包裹,所有內容都在裡面 |
使用情境 | 傳統的 HTML <form> 標籤提交 |
現代前後端分離架構,由 JavaScript (如 Vue, React) 發送的 AJAX/Fetch 請求 |
數量限制 | 一個方法中可以有多個 | 一個方法中只能有一個 |
簡單判斷法則:
<form action="..." method="post">
提交的嗎?用 @ModelAttribute
。@RequestBody
。@ModelAttribute
?第一步:建立一個資料傳輸物件 (Data Transfer Object, DTO)
這個 DTO 物件的屬性,要和 HTML 表單中 <input>
標籤的 name
屬性相對應。
public class UserRegistrationDto {
private String username;
private String password;
private String email;
// ... Getters and Setters ...
}
第二步:在 Controller 方法中使用 @ModelAttribute
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller // 注意:這裡是 @Controller,因為通常會搭配回傳網頁視圖
public class RegistrationController {
@PostMapping("/register")
@ResponseBody // 為了方便演示,我們直接回傳字串,實務上可能會重導向到成功頁面
public String processRegistration(@ModelAttribute UserRegistrationDto userDto) {
// 當程式執行到這裡,Spring 已經自動將表單資料填充到 userDto 物件中了!
System.out.println("新註冊使用者: " + userDto.getUsername());
return "註冊成功!歡迎 " + userDto.getUsername();
}
}
當使用者提交表單時,Spring 會自動建立 UserRegistrationDto
物件,並根據請求參數的名稱 (username
, password
...) 呼叫對應的 setter
方法,最後將填充好的物件傳遞給你的方法。
恭喜你完成了本日的學習!現在,你已經掌握了 Spring Boot 中處理各種請求資訊的全面技能:
@PathVariable
/ @RequestParam
:處理來自 URL 的資料。@RequestBody
:處理來自 Request Body 的 JSON 資料,用於現代 API。@RequestHeader
:處理來自 Request Header 的元資料,常用於認證、獲取客戶端資訊。@CookieValue
:處理來自 Cookie 的資料,常用於會話管理。@ModelAttribute
:處理來自 HTML 表單的參數,常用於傳統 MVC 應用。你現在已經具備處理更複雜 Web 互動的能力,例如開發一個需要檢查 Authorization
標頭才有權限訪問的 API,或是根據使用者的 Cookie 提供個人化的內容。繼續加油!