在 SpringBoot 開發中,處理 HTTP 請求參數是我們每天都要面對的工作。而@RequestParam
和@RequestBody
這兩個注解,就像是我們手中的兩把利劍,既能高效解決問題,用不好也可能 "誤傷" 自己。
作為一名資深 Java 開發者,我見過太多因為對這兩個注解理解不透徹而導致的生產事故:有人把 JSON 參數用@RequestParam
接收導致接口一直報 400 錯誤,有人在 GET 請求里用@RequestBody
接收參數結果百思不得其解,還有人因為不清楚參數綁定的優先級而寫出難以維護的代碼...
本文將從基礎到進階,全方位剖析這兩個注解的使用場景、底層原理、常見問題和最佳實踐,帶你徹底掌握它們的精髓,讓你的接口參數處理代碼既優雅又健壯!
一、初識 @RequestParam:URL 中的參數捕獲器
@RequestParam
是 SpringMVC 中最常用的注解之一,用于從 HTTP 請求的參數中提取數據并綁定到控制器方法的參數上。它看似簡單,實則暗藏玄機。
1.1 @RequestParam 的基本用法
@RequestParam
主要用于獲取 HTTP 請求中的查詢參數(query parameters),也就是 URL 中?
后面的鍵值對。例如在http://localhost:8080/user?name=張三&age=20
這個 URL 中,name
和age
就是查詢參數。
基本語法如下:
java
@RequestMapping("/user")
public String getUser(@RequestParam String name,@RequestParam int age) {return "姓名:" + name + ",年齡:" + age;
}
這段代碼會自動從請求參數中獲取name
和age
的值,并分別賦值給方法參數。
1.2 @RequestParam 的屬性詳解
@RequestParam
注解有三個重要屬性,掌握它們能讓你更靈活地使用這個注解:
value
/name
:指定請求參數的名稱,如果方法參數名與請求參數名一致,可以省略required
:表示該參數是否必須,默認值為true
defaultValue
:指定參數的默認值,當參數不存在時使用
下面通過示例詳細說明:
1.2.1 指定參數名稱
當方法參數名與請求參數名不一致時,需要通過value
或name
屬性指定:
java
@RequestMapping("/user")
public String getUser(@RequestParam(value = "userName") String name, // 請求參數是userName,綁定到name變量@RequestParam(name = "userAge") int age) { // 請求參數是userAge,綁定到age變量return "姓名:" + name + ",年齡:" + age;
}
此時,我們需要用http://localhost:8080/user?userName=張三&userAge=20
來訪問接口。
1.2.2 處理非必需參數
當required
屬性設為false
時,表示該參數不是必需的:
java
@RequestMapping("/search")
public String search(@RequestParam String keyword,@RequestParam(required = false) Integer page) { // page參數非必需// 如果page為null,則默認查詢第一頁int currentPage = (page == null) ? 1 : page;return "搜索關鍵詞:" + keyword + ",頁碼:" + currentPage;
}
這個接口可以通過http://localhost:8080/search?keyword=java
(不帶 page 參數)訪問,也可以通過http://localhost:8080/search?keyword=java&page=2
訪問。
如果一個required=true
的參數未提供,Spring 會拋出MissingServletRequestParameterException
異常,導致接口返回 400 Bad Request 錯誤。
1.2.3 設置默認值
defaultValue
屬性可以為參數設置默認值,當參數不存在時自動使用該值:
java
@RequestMapping("/search")
public String search(@RequestParam String keyword,@RequestParam(defaultValue = "1") int page, // 默認頁碼為1@RequestParam(defaultValue = "10") int size) { // 默認每頁10條return "搜索關鍵詞:" + keyword + ",頁碼:" + page + ",每頁條數:" + size;
}
這個接口有以下幾種訪問方式:
http://localhost:8080/search?keyword=java
?→ page=1, size=10http://localhost:8080/search?keyword=java&page=2
?→ page=2, size=10http://localhost:8080/search?keyword=java&page=3&size=20
?→ page=3, size=20
需要注意的是,設置了defaultValue
后,required
屬性會自動變為false
,即使你顯式設置required=true
也會被忽略。
1.3 @RequestParam 處理數組和集合
@RequestParam
不僅能處理基本類型,還能直接綁定數組和集合類型的參數。
1.3.1 處理數組
當請求參數有多個相同名稱時,可以用數組接收:
java
@RequestMapping("/array")
public String handleArray(@RequestParam String[] hobbies) {return "愛好:" + Arrays.toString(hobbies);
}
訪問http://localhost:8080/array?hobbies=讀書&hobbies=運動&hobbies=編程
,會得到結果:愛好:[讀書, 運動, 編程]
1.3.2 處理集合
處理集合時,需要指定value
屬性,并且最好指定required
屬性:
java
@RequestMapping("/list")
public String handleList(@RequestParam(value = "ids", required = false) List<Integer> ids) {return "IDs:" + ids;
}
訪問http://localhost:8080/list?ids=1&ids=2&ids=3
,會得到結果:IDs:[1, 2, 3]
如果需要處理集合,Spring 需要知道集合的泛型類型。對于基本類型,Spring 可以自動推斷,但對于自定義類型,可能需要額外配置。
1.4 @RequestParam 的使用場景
@RequestParam
適用于以下場景:
- GET 請求參數:GET 請求的參數通常放在 URL 的查詢字符串中,這是
@RequestParam
最主要的應用場景 - 表單提交的參數:表單默認使用
application/x-www-form-urlencoded
格式提交,其參數也可以用@RequestParam
接收 - 簡單參數傳遞:適用于傳遞簡單類型的數據(字符串、數字等)
- URL 路徑中的查詢參數:無論 HTTP 方法是什么,只要參數在 URL 的查詢字符串中,都可以用
@RequestParam
接收
1.5 @RequestParam 的常見問題與解決方案
1.5.1 參數類型不匹配
當請求參數的類型與方法參數的類型不匹配時,會拋出TypeMismatchException
異常。
例如,下面的接口期望接收一個整數:
java
@RequestMapping("/number")
public String handleNumber(@RequestParam int number) {return "數字:" + number;
}
如果我們用http://localhost:8080/number?number=abc
訪問,會得到 400 錯誤,因為 "abc" 無法轉換為整數。
解決方案:
- 使用包裝類型(如
Integer
代替int
),這樣當參數類型不匹配時會得到null
而不是拋出異常 - 添加參數校驗
- 使用
@ExceptionHandler
全局處理類型轉換異常
java
// 改進方案
@RequestMapping("/number")
public String handleNumber(@RequestParam(required = false) Integer number) {if (number == null) {return "請提供有效的數字參數";}return "數字:" + number;
}
1.5.2 中文亂碼問題
當請求參數包含中文時,可能會出現亂碼問題。這通常是由于 URL 編碼與服務器解碼使用的字符集不一致導致的。
解決方案:
- 確保請求 URL 的中文參數進行了正確編碼(通常是 UTF-8)
- 在 SpringBoot 中配置字符編碼過濾器
java
@Configuration
public class WebConfig implements WebMvcConfigurer {@Beanpublic FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {FilterRegistrationBean<CharacterEncodingFilter> registrationBean = new FilterRegistrationBean<>();CharacterEncodingFilter filter = new CharacterEncodingFilter();filter.setEncoding("UTF-8");filter.setForceEncoding(true);registrationBean.setFilter(filter);registrationBean.addUrlPatterns("/*");return registrationBean;}
}
在 SpringBoot 2.x 及以上版本,默認已經配置了 UTF-8 編碼,一般不需要額外配置,但如果出現亂碼問題,上述配置可以作為解決方案。
1.5.3 參數名與 Java 關鍵字沖突
有時請求參數名可能與 Java 關鍵字重名(如int
、class
等),這時候直接使用關鍵字作為方法參數名會導致編譯錯誤。
解決方案:使用@RequestParam
的value
屬性指定參數名,方法參數名使用其他合法名稱
java
@RequestMapping("/keyword")
public String handleKeyword(@RequestParam(value = "class") String className) {return "班級:" + className;
}
這樣就可以處理http://localhost:8080/keyword?class=一班
這樣的請求了。
二、深入 @RequestBody:請求體的解析能手
@RequestBody
注解用于將 HTTP 請求的正文(body)解析并綁定到控制器方法的參數上。它主要用于處理非application/x-www-form-urlencoded
格式的請求數據,如 JSON、XML 等。
2.1 @RequestBody 的基本用法
@RequestBody
通常用于 POST、PUT 等 HTTP 方法,這些方法的參數通常放在請求體中而不是 URL 中。
基本語法如下:
java
@PostMapping("/user")
public String createUser(@RequestBody User user) {return "創建用戶:" + user.getName() + ",年齡:" + user.getAge();
}// User類定義
public class User {private String name;private int age;// 必須有默認構造函數public User() {}// getter和setter方法public String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }
}
當我們向http://localhost:8080/user
發送 POST 請求,并且請求體是 JSON 格式:
json
{"name": "張三","age": 20
}
Spring 會自動將 JSON 數據解析為 User 對象,并傳遞給 createUser 方法。
2.2 @RequestBody 支持的數據格式
@RequestBody
支持多種數據格式,這取決于項目中配置的消息轉換器(MessageConverter)。SpringBoot 默認配置了以下幾種常用的消息轉換器:
- JSON:通過 Jackson 庫支持,是最常用的格式
- XML:通過 JAXB 支持,如果添加了相應依賴
- Form 表單:
application/x-www-form-urlencoded
格式,但通常用@RequestParam
處理 - Multipart:
multipart/form-data
格式,用于文件上傳
要使用特定的數據格式,需要確保請求頭中的Content-Type
與實際數據格式一致:
- JSON:
Content-Type: application/json
- XML:
Content-Type: application/xml
?或?text/xml
2.3 @RequestBody 與簡單類型
雖然@RequestBody
主要用于綁定復雜對象,但也可以用于綁定簡單類型,如字符串、數字等:
java
@PostMapping("/message")
public String handleMessage(@RequestBody String message) {return "收到消息:" + message;
}
當發送包含純文本的請求體時,這段代碼會直接將文本內容綁定到 message 參數。
但需要注意的是,@RequestBody
一次只能綁定一個簡單類型參數,因為整個請求體只能解析為一個值。如果需要傳遞多個簡單參數,應該使用對象包裝它們,或者考慮使用@RequestParam
。
2.4 @RequestBody 與集合類型
@RequestBody
可以直接綁定集合類型,例如List
、Map
等:
2.4.1 綁定 List
java
@PostMapping("/users")
public String createUsers(@RequestBody List<User> users) {return "創建用戶數量:" + users.size() + ",第一個用戶:" + users.get(0).getName();
}
對應的 JSON 請求體:
json
[{"name": "張三", "age": 20},{"name": "李四", "age": 22}
]
2.4.2 綁定 Map
java
@PostMapping("/info")
public String handleInfo(@RequestBody Map<String, Object> info) {return "姓名:" + info.get("name") + ",愛好:" + info.get("hobby");
}
對應的 JSON 請求體:
json
{"name": "張三","hobby": "編程"
}
2.5 @RequestBody 的使用場景
@RequestBody
適用于以下場景:
- POST/PUT 請求:這些請求通常需要傳遞復雜的數據,適合放在請求體中
- JSON/XML 數據:處理結構化的復雜數據時,
@RequestBody
能自動完成對象映射 - RESTful API:在 RESTful 風格的 API 中,創建和更新資源通常使用
@RequestBody
接收數據 - 傳遞大量數據:當需要傳遞的數據量較大時,放在請求體中比放在 URL 中更合適
- 復雜對象結構:當參數是多層嵌套的復雜對象時,
@RequestBody
能簡化參數綁定
2.6 @RequestBody 的常見問題與解決方案
2.6.1 400 Bad Request 錯誤
這是使用@RequestBody
時最常見的錯誤,通常有以下幾種原因:
- 請求體為空:當
@RequestBody
標注的參數是必需的,但請求體為空時 - JSON 格式錯誤:請求體的 JSON 格式不正確(如缺少引號、括號不匹配等)
- 類型不匹配:JSON 中的字段類型與 Java 對象的字段類型不匹配
- 缺少默認構造函數:Java 對象沒有提供默認的無參構造函數
解決方案:
- 對于非必需參數,可以結合
required
屬性(默認為 true):
java
@PostMapping("/user")
public String createUser(@RequestBody(required = false) User user) {if (user == null) {return "未提供用戶信息";}return "創建用戶:" + user.getName();
}
- 確保 JSON 格式正確,可以使用 JSON 校驗工具驗證
- 檢查 Java 對象是否有默認構造函數
- 添加全局異常處理,捕獲并處理 JSON 解析異常:
java
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(HttpMessageNotReadableException.class)public String handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {return "請求數據格式錯誤:" + e.getMessage();}
}
2.6.2 字段不匹配問題
當 JSON 中的字段名與 Java 對象的字段名不一致時,會導致字段無法正確綁定。
解決方案:使用 Jackson 的@JsonProperty
注解指定映射關系
java
public class User {@JsonProperty("user_name") // 與JSON中的user_name字段對應private String name;@JsonProperty("user_age") // 與JSON中的user_age字段對應private int age;// getter、setter和構造函數省略
}
這樣,即使 JSON 中的字段名與 Java 對象不同,也能正確綁定:
json
{"user_name": "張三","user_age": 20
}
2.6.3 日期類型處理
日期類型的 JSON 字符串(如 "2023-10-01" 或 "2023/10/01 12:00:00")在默認情況下可能無法正確解析為 Java 的Date
或LocalDateTime
類型。
解決方案:
- 使用
@JsonFormat
注解指定日期格式:
java
public class Order {private String orderId;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private LocalDateTime createTime;// getter、setter和構造函數省略
}
- 配置全局日期格式(推薦):
java
@Configuration
public class JacksonConfig {@Beanpublic ObjectMapper objectMapper() {ObjectMapper objectMapper = new ObjectMapper();JavaTimeModule javaTimeModule = new JavaTimeModule();// 配置LocalDateTime的序列化和反序列化格式javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));objectMapper.registerModule(javaTimeModule);// 忽略未知屬性,避免因JSON中有額外字段而報錯objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);return objectMapper;}
}
三、@RequestParam 與 @RequestBody 的終極對比
雖然@RequestParam
和@RequestBody
都是用于獲取請求參數的注解,但它們在很多方面都有顯著區別。理解這些區別是正確使用它們的關鍵。
3.1 數據位置不同
這是兩者最根本的區別:
@RequestParam
:獲取 URL 查詢參數(query parameters)或表單提交的參數(application/x-www-form-urlencoded
)@RequestBody
:獲取 HTTP 請求體(request body)中的數據
形象地說,@RequestParam
處理的是 URL 中?
后面的數據,而@RequestBody
處理的是 HTTP 請求中獨立于 URL 的正文部分。
3.2 支持的 HTTP 方法不同
@RequestParam
:通常用于 GET 方法,也可以用于 POST、PUT 等方法(當參數在 URL 或表單中時)@RequestBody
:通常用于 POST、PUT、PATCH 等方法,不建議用于 GET 方法(GET 方法通常沒有請求體)
雖然 HTTP 規范并沒有禁止 GET 方法有請求體,但大多數瀏覽器和服務器對 GET 請求體的支持并不完善,因此實際開發中應避免在 GET 方法中使用@RequestBody
。
3.3 數據格式不同
@RequestParam
:主要處理鍵值對形式的數據(key1=value1&key2=value2
)@RequestBody
:主要處理結構化數據,如 JSON、XML 等
@RequestParam
可以處理表單提交的application/x-www-form-urlencoded
格式數據,而@RequestBody
可以處理更復雜的結構化數據。
3.4 參數數量不同
@RequestParam
:可以同時獲取多個參數,每個參數對應 URL 或表單中的一個鍵值對@RequestBody
:通常一次只能綁定一個參數,因為整個請求體只能解析為一個對象
如果需要獲取多個參數,@RequestBody
通常會將這些參數封裝到一個 Java 對象中。
3.5 適用場景不同
場景 | 推薦使用 | 原因 |
---|---|---|
獲取簡單參數 | @RequestParam | 簡單直接,無需額外對象 |
表單提交 | @RequestParam | 表單默認使用 application/x-www-form-urlencoded 格式 |
RESTful API 創建資源 | @RequestBody | 適合傳遞復雜對象,符合 REST 風格 |
傳遞大量數據 | @RequestBody | URL 長度有限制,請求體可以容納更多數據 |
傳遞復雜對象 | @RequestBody | 自動映射復雜對象,包括嵌套結構 |
分頁查詢參數 | @RequestParam | 分頁參數通常是簡單值,適合放在 URL 中 |
搜索過濾條件 | @RequestParam 或 @RequestBody | 簡單條件用 @RequestParam,復雜條件用 @RequestBody |
3.6 混合使用示例
在實際開發中,我們經常需要同時使用@RequestParam
和@RequestBody
:
java
@PostMapping("/orders")
public String createOrder(@RequestParam String userId, // 從URL參數獲取用戶ID@RequestParam(required = false) String couponCode, // 從URL參數獲取優惠券代碼(可選)@RequestBody OrderRequest orderRequest) { // 從請求體獲取訂單詳情return "用戶ID:" + userId + ",優惠券:" + (couponCode != null ? couponCode : "無") + ",訂單商品:" + orderRequest.getProducts().size() + "件";
}// OrderRequest類
public class OrderRequest {private List<Product> products;private String shippingAddress;private String paymentMethod;// getter、setter和構造函數省略
}
這個接口可以通過以下方式調用:
- URL:
http://localhost:8080/orders?userId=123&couponCode=SAVE10
- 請求方法:POST
- Content-Type:application/json
- 請求體:
json
{"products": [{"id": "p1", "name": "手機", "quantity": 1},{"id": "p2", "name": "耳機", "quantity": 2}],"shippingAddress": "北京市朝陽區","paymentMethod": "支付寶"
}
這種混合使用的方式結合了兩種注解的優勢,適用于既需要簡單參數又需要復雜對象的場景。
四、高級應用與最佳實踐
掌握了@RequestParam
和@RequestBody
的基本用法后,我們來看看一些高級應用和最佳實踐,幫助你寫出更優雅、更健壯的代碼。
4.1 參數校驗
無論是@RequestParam
還是@RequestBody
綁定的參數,都需要進行合法性校驗,以確保系統安全和數據正確。
Spring 支持 JSR-303/JSR-380 規范的參數校驗,可以通過注解輕松實現:
java
@PostMapping("/user")
public String createUser(@RequestParam @NotBlank(message = "用戶名不能為空") String username,@RequestParam @Min(value = 18, message = "年齡不能小于18歲") int age,@RequestBody @Valid UserDetail detail) {return "創建用戶:" + username;
}// UserDetail類
public class UserDetail {@NotBlank(message = "郵箱不能為空")@Email(message = "郵箱格式不正確")private String email;@Size(min = 6, max = 20, message = "密碼長度必須在6-20之間")private String password;// getter、setter和構造函數省略
}
要使用參數校驗,需要添加以下依賴:
xml
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>
并在全局異常處理器中處理校驗失敗的異常:
java
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(MethodArgumentNotValidException.class)public String handleMethodArgumentNotValid(MethodArgumentNotValidException e) {// 獲取所有校驗失敗的消息List<String> errorMessages = e.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());return "參數校驗失敗:" + String.join(";", errorMessages);}@ExceptionHandler(ConstraintViolationException.class)public String handleConstraintViolation(ConstraintViolationException e) {List<String> errorMessages = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());return "參數校驗失敗:" + String.join(";", errorMessages);}
}
4.2 自定義參數解析器
當@RequestParam
和@RequestBody
不能滿足需求時,我們可以自定義參數解析器來處理特殊的參數綁定邏輯。
例如,我們可以創建一個@CurrentUser
注解,用于直接獲取當前登錄用戶:
java
// 自定義注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}// 自定義參數解析器
@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {@Overridepublic boolean supportsParameter(MethodParameter parameter) {// 只處理帶有@CurrentUser注解的User類型參數return parameter.hasParameterAnnotation(CurrentUser.class) && parameter.getParameterType().equals(User.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {// 從請求中獲取當前登錄用戶ID(實際項目中可能從Token、Session等獲取)String userId = webRequest.getHeader("X-User-Id");// 根據用戶ID查詢用戶信息(實際項目中可能是從數據庫或緩存獲取)User currentUser = new User();currentUser.setId(userId);currentUser.setName("當前登錄用戶");return currentUser;}
}// 注冊解析器
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(new CurrentUserArgumentResolver());}
}// 使用示例
@GetMapping("/profile")
public String getProfile(@CurrentUser User currentUser) {return "當前登錄用戶:" + currentUser.getName() + ",ID:" + currentUser.getId();
}
這個例子展示了如何通過自定義參數解析器擴展 Spring 的參數綁定能力,在實際項目中非常實用。
4.3 處理文件上傳
文件上傳是 Web 開發中的常見需求,Spring 提供了@RequestParam
結合MultipartFile
來處理文件上傳:
java
@PostMapping("/upload")
public String uploadFile(@RequestParam String description, // 普通參數@RequestParam("file") MultipartFile file) { // 文件參數if (file.isEmpty()) {return "請選擇要上傳的文件";}try {// 獲取文件名String fileName = file.getOriginalFilename();// 獲取文件內容byte[] bytes = file.getBytes();// 保存文件(實際項目中通常保存到磁盤或云存儲)Path path = Paths.get("uploads/" + fileName);Files.write(path, bytes);return "文件上傳成功:" + fileName + ",描述:" + description;} catch (IOException e) {return "文件上傳失敗:" + e.getMessage();}
}
多文件上傳可以使用MultipartFile
數組:
java
@PostMapping("/upload-multiple")
public String uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {List<String> uploadedFiles = new ArrayList<>();for (MultipartFile file : files) {if (!file.isEmpty()) {try {String fileName = file.getOriginalFilename();Path path = Paths.get("uploads/" + fileName);Files.write(path, file.getBytes());uploadedFiles.add(fileName);} catch (IOException e) {return "文件" + file.getOriginalFilename() + "上傳失敗:" + e.getMessage();}}}return "成功上傳文件:" + String.join(",", uploadedFiles);
}
需要注意的是,文件上傳的表單需要設置enctype="multipart/form-data"
屬性:
html
預覽
<form method="post" action="/upload" enctype="multipart/form-data"><input type="text" name="description" /><input type="file" name="file" /><button type="submit">上傳</button>
</form>
在 SpringBoot 中,默認的文件上傳大小限制可能比較小,可以通過配置修改:
properties
# 單個文件大小限制
spring.servlet.multipart.max-file-size=10MB
# 總請求大小限制
spring.servlet.multipart.max-request-size=100MB
4.4 RESTful API 設計中的最佳實踐
在 RESTful API 設計中,合理使用@RequestParam
和@RequestBody
能讓 API 更加規范和易用:
GET 請求:使用
@RequestParam
獲取查詢參數,用于查詢、過濾、分頁等操作java
@GetMapping("/users") public Page<User> getUsers(@RequestParam(required = false) String name, // 可選的姓名過濾@RequestParam(defaultValue = "0") int page, // 頁碼,默認0@RequestParam(defaultValue = "10") int size) { // 每頁條數,默認10// 查詢邏輯 }
POST 請求:使用
@RequestBody
創建資源,請求體包含資源的完整信息java
@PostMapping("/users") public User createUser(@RequestBody @Valid UserCreateRequest request) {// 創建用戶邏輯 }
PUT 請求:使用
@RequestBody
更新資源,通常包含資源的完整信息java
@PutMapping("/users/{id}") public User updateUser(@PathVariable String id, // 路徑參數,用戶ID@RequestBody @Valid UserUpdateRequest request) {// 更新用戶邏輯 }
PATCH 請求:使用
@RequestBody
部分更新資源,通常只包含需要更新的字段java
@PatchMapping("/users/{id}") public User partialUpdateUser(@PathVariable String id,@RequestBody Map<String, Object> updates) { // 只包含需要更新的字段// 部分更新邏輯 }
DELETE 請求:通常使用路徑參數指定要刪除的資源 ID,復雜情況可結合
@RequestParam
java
@DeleteMapping("/users/{id}") public void deleteUser(@PathVariable String id) {// 刪除用戶邏輯 }
遵循這些實踐可以使你的 API 更加直觀和易用,符合 RESTful 設計原則。
五、底層原理探秘
了解@RequestParam
和@RequestBody
的底層原理,不僅能幫助我們更好地理解它們的行為,還能在遇到問題時更快地定位原因。
5.1 SpringMVC 的請求處理流程
SpringMVC 處理一個 HTTP 請求的大致流程如下:
- 客戶端發送 HTTP 請求到 DispatcherServlet
- DispatcherServlet 根據請求 URL 查找對應的 Handler(通常是控制器方法)
- 找到 Handler 后,DispatcherServlet 調用 HandlerAdapter 處理請求
- HandlerAdapter 負責解析請求參數,并綁定到 Handler 的方法參數上
- 執行 Handler 方法,得到返回結果
- HandlerAdapter 將返回結果封裝成 ModelAndView
- DispatcherServlet 根據 ModelAndView 選擇合適的 ViewResolver 渲染視圖
- 將渲染結果返回給客戶端
@RequestParam
和@RequestBody
的處理發生在第 4 步,由特定的參數解析器完成。
5.2 @RequestParam 的解析原理
@RequestParam
的解析主要由RequestParamMethodArgumentResolver
類完成,其工作流程如下:
- 檢查方法參數是否標注了
@RequestParam
注解 - 如果標注了,從請求對象(HttpServletRequest)中獲取參數值:
- 首先調用
request.getParameter(name)
獲取參數值 - 這方法會同時查找查詢參數和表單參數(
application/x-www-form-urlencoded
)
- 首先調用
- 如果參數是數組或集合,將獲取到的多個值轉換為對應的數組或集合
- 進行類型轉換,將字符串參數轉換為方法參數的類型
- 將轉換后的值綁定到方法參數上
request.getParameter(name)
方法的底層實現會根據請求的Content-Type
不同而有不同的處理邏輯:
- 對于 GET 請求,從 URL 的查詢字符串中解析參數
- 對于 POST 請求且
Content-Type
為application/x-www-form-urlencoded
,從請求體中解析參數 - 其他類型的請求,主要從 URL 的查詢字符串中解析參數
5.3 @RequestBody 的解析原理
@RequestBody
的解析主要由RequestResponseBodyMethodProcessor
類完成,其工作流程如下:
- 檢查方法參數是否標注了
@RequestBody
注解 - 從請求對象中獲取輸入流(InputStream)
- 根據請求頭中的
Content-Type
選擇合適的 HttpMessageConverter - 使用選中的 HttpMessageConverter 將請求體內容轉換為方法參數的類型
- 將轉換后的對象綁定到方法參數上
HttpMessageConverter 是一個接口,不同的實現類處理不同的數據格式:
MappingJackson2HttpMessageConverter
:處理 JSON 格式(默認包含)Jaxb2RootElementHttpMessageConverter
:處理 XML 格式(需要相應依賴)StringHttpMessageConverter
:處理字符串格式FormHttpMessageConverter
:處理表單數據
Spring 會根據Content-Type
請求頭和目標類型來選擇最合適的 HttpMessageConverter。
5.4 參數解析器的優先級
SpringMVC 中有多種參數解析器,它們有不同的優先級:
RequestParamMethodArgumentResolver
(處理@RequestParam
)RequestResponseBodyMethodProcessor
(處理@RequestBody
)- 其他解析器(如處理路徑參數的
PathVariableMethodArgumentResolver
等)
這種優先級意味著在解析參數時,Spring 會先嘗試用@RequestParam
的解析器處理,再嘗試用@RequestBody
的解析器處理。
但實際上,由于@RequestParam
和@RequestBody
的適用場景不同(一個處理參數,一個處理請求體),它們很少會產生沖突。
六、常見面試題解析
@RequestParam
和@RequestBody
是 Java 面試中經常被問到的知識點,掌握以下常見問題的答案,能讓你在面試中更有優勢。
6.1 @RequestParam 和 @RequestBody 的區別是什么?
這是最基礎也最常被問到的問題,回答時應涵蓋數據位置、適用場景、支持的數據格式等方面:
@RequestParam
和@RequestBody
都是 SpringMVC 中用于獲取請求數據的注解,主要區別如下:
- 數據位置不同:
@RequestParam
用于獲取 URL 查詢參數或表單參數,@RequestBody
用于獲取請求體中的數據 - 適用 HTTP 方法:
@RequestParam
常用于 GET 方法,@RequestBody
常用于 POST、PUT 等方法 - 數據格式:
@RequestParam
處理鍵值對形式的數據,@RequestBody
處理 JSON、XML 等結構化數據 - 參數數量:
@RequestParam
可以同時獲取多個參數,@RequestBody
通常一次處理一個對象 - 使用場景:簡單參數用
@RequestParam
,復雜對象用@RequestBody
6.2 什么時候用 @RequestParam,什么時候用 @RequestBody?
回答這個問題時,應結合具體場景說明:
選擇使用哪個注解主要取決于參數的類型、位置和復雜度:
- 當參數是簡單類型(字符串、數字等)且位于 URL 查詢字符串或表單中時,使用
@RequestParam
- 當參數是復雜對象(尤其是包含嵌套結構的對象)時,使用
@RequestBody
- GET 請求通常使用
@RequestParam
,因為 GET 請求的參數通常在 URL 中 - POST、PUT 等請求如果需要傳遞復雜數據,使用
@RequestBody
- 表單提交(
application/x-www-form-urlencoded
)使用@RequestParam
- JSON 或 XML 格式的數據使用
@RequestBody
簡單來說,簡單參數用@RequestParam
,復雜對象用@RequestBody
;URL 中的參數用@RequestParam
,請求體中的數據用@RequestBody
。
6.3 GET 請求可以用 @RequestBody 嗎?
這個問題考察對 HTTP 規范和 SpringMVC 實現的理解:
從技術上講,SpringMVC 允許在 GET 請求中使用@RequestBody
,但這是不推薦的做法,原因如下:
- HTTP 規范:GET 請求的主要目的是獲取資源,通常不應該有請求體。雖然 HTTP 規范沒有明確禁止 GET 請求有請求體,但很多服務器和代理對 GET 請求體的支持并不完善。
- 實際問題:
- 一些瀏覽器會忽略 GET 請求的請求體
- 緩存機制通常基于 URL,GET 請求體不會被納入緩存鍵的計算
- 日志系統通常不會記錄 GET 請求體,不利于問題排查
- 設計原則:RESTful 設計原則中,GET 請求用于查詢,參數應該放在 URL 中,這也是
@RequestParam
的典型應用場景
因此,實際開發中應避免在 GET 請求中使用@RequestBody
,堅持使用@RequestParam
獲取 GET 請求的參數。
6.4 @RequestParam 的 required 屬性為 true 但參數不存在會怎樣?
這個問題考察對異常處理的理解:
當@RequestParam
的required
屬性為true
(默認值)但請求中沒有對應的參數時,SpringMVC 會拋出MissingServletRequestParameterException
異常。
如果沒有全局異常處理器處理這個異常,SpringMVC 會返回 400 Bad Request 響應給客戶端。
為了提供更友好的錯誤信息,建議通過全局異常處理器捕獲并處理這個異常:
java
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(MissingServletRequestParameterException.class)public String handleMissingParameter(MissingServletRequestParameterException e) {return "缺少必要參數:" + e.getParameterName();}
}
另外,可以通過設置required = false
并提供默認值來避免這種異常:
java
@RequestParam(required = false, defaultValue = "default") String param
6.5 @RequestBody 如何處理日期類型?
這個問題考察對實際開發中常見問題的處理能力:
@RequestBody
處理日期類型需要考慮 JSON 字符串到日期對象的轉換,常用的解決方案有:
使用 @JsonFormat 注解:在實體類的日期字段上添加注解,指定日期格式
java
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime createTime;
配置全局日期格式:通過配置 Jackson 的 ObjectMapper,設置全局的日期序列化和反序列化格式
java
@Bean public ObjectMapper objectMapper() {ObjectMapper objectMapper = new ObjectMapper();JavaTimeModule module = new JavaTimeModule();module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));objectMapper.registerModule(module);return objectMapper; }
使用自定義序列化器 / 反序列化器:對于特殊的日期格式,可以編寫自定義的序列化器和反序列化器
選擇哪種方案取決于項目需求:單個字段的特殊格式用@JsonFormat
,統一的日期格式用全局配置。
七、總結與展望
@RequestParam
和@RequestBody
是 SpringBoot 開發中處理 HTTP 請求參數的核心注解,掌握它們的使用方法和底層原理,對于編寫高質量的 Web 應用至關重要。