在 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 提供個人化的內容。繼續加油!