SpringBoot 注解深剖:@RequestParam 與 @RequestBody 的終極對決,90% 的開發者都踩過這些坑!

在 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 中,nameage就是查詢參數。

基本語法如下:

java

@RequestMapping("/user")
public String getUser(@RequestParam String name,@RequestParam int age) {return "姓名:" + name + ",年齡:" + age;
}

這段代碼會自動從請求參數中獲取nameage的值,并分別賦值給方法參數。

1.2 @RequestParam 的屬性詳解

@RequestParam注解有三個重要屬性,掌握它們能讓你更靈活地使用這個注解:

  • value/name:指定請求參數的名稱,如果方法參數名與請求參數名一致,可以省略
  • required:表示該參數是否必須,默認值為true
  • defaultValue:指定參數的默認值,當參數不存在時使用

下面通過示例詳細說明:

1.2.1 指定參數名稱

當方法參數名與請求參數名不一致時,需要通過valuename屬性指定:

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=10
  • http://localhost:8080/search?keyword=java&page=2?→ page=2, size=10
  • http://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適用于以下場景:

  1. GET 請求參數:GET 請求的參數通常放在 URL 的查詢字符串中,這是@RequestParam最主要的應用場景
  2. 表單提交的參數:表單默認使用application/x-www-form-urlencoded格式提交,其參數也可以用@RequestParam接收
  3. 簡單參數傳遞:適用于傳遞簡單類型的數據(字符串、數字等)
  4. 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" 無法轉換為整數。

解決方案

  1. 使用包裝類型(如Integer代替int),這樣當參數類型不匹配時會得到null而不是拋出異常
  2. 添加參數校驗
  3. 使用@ExceptionHandler全局處理類型轉換異常

java

// 改進方案
@RequestMapping("/number")
public String handleNumber(@RequestParam(required = false) Integer number) {if (number == null) {return "請提供有效的數字參數";}return "數字:" + number;
}
1.5.2 中文亂碼問題

當請求參數包含中文時,可能會出現亂碼問題。這通常是由于 URL 編碼與服務器解碼使用的字符集不一致導致的。

解決方案

  1. 確保請求 URL 的中文參數進行了正確編碼(通常是 UTF-8)
  2. 在 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 關鍵字重名(如intclass等),這時候直接使用關鍵字作為方法參數名會導致編譯錯誤。

解決方案:使用@RequestParamvalue屬性指定參數名,方法參數名使用其他合法名稱

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 默認配置了以下幾種常用的消息轉換器:

  1. JSON:通過 Jackson 庫支持,是最常用的格式
  2. XML:通過 JAXB 支持,如果添加了相應依賴
  3. Form 表單application/x-www-form-urlencoded格式,但通常用@RequestParam處理
  4. Multipartmultipart/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可以直接綁定集合類型,例如ListMap等:

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適用于以下場景:

  1. POST/PUT 請求:這些請求通常需要傳遞復雜的數據,適合放在請求體中
  2. JSON/XML 數據:處理結構化的復雜數據時,@RequestBody能自動完成對象映射
  3. RESTful API:在 RESTful 風格的 API 中,創建和更新資源通常使用@RequestBody接收數據
  4. 傳遞大量數據:當需要傳遞的數據量較大時,放在請求體中比放在 URL 中更合適
  5. 復雜對象結構:當參數是多層嵌套的復雜對象時,@RequestBody能簡化參數綁定

2.6 @RequestBody 的常見問題與解決方案

2.6.1 400 Bad Request 錯誤

這是使用@RequestBody時最常見的錯誤,通常有以下幾種原因:

  1. 請求體為空:當@RequestBody標注的參數是必需的,但請求體為空時
  2. JSON 格式錯誤:請求體的 JSON 格式不正確(如缺少引號、括號不匹配等)
  3. 類型不匹配:JSON 中的字段類型與 Java 對象的字段類型不匹配
  4. 缺少默認構造函數: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 的DateLocalDateTime類型。

解決方案

  1. 使用@JsonFormat注解指定日期格式:

java

public class Order {private String orderId;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private LocalDateTime createTime;// getter、setter和構造函數省略
}

  1. 配置全局日期格式(推薦):

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 風格
傳遞大量數據@RequestBodyURL 長度有限制,請求體可以容納更多數據
傳遞復雜對象@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 更加規范和易用:

  1. 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// 查詢邏輯
    }
    
  2. POST 請求:使用@RequestBody創建資源,請求體包含資源的完整信息

    java

    @PostMapping("/users")
    public User createUser(@RequestBody @Valid UserCreateRequest request) {// 創建用戶邏輯
    }
    
  3. PUT 請求:使用@RequestBody更新資源,通常包含資源的完整信息

    java

    @PutMapping("/users/{id}")
    public User updateUser(@PathVariable String id,  // 路徑參數,用戶ID@RequestBody @Valid UserUpdateRequest request) {// 更新用戶邏輯
    }
    
  4. PATCH 請求:使用@RequestBody部分更新資源,通常只包含需要更新的字段

    java

    @PatchMapping("/users/{id}")
    public User partialUpdateUser(@PathVariable String id,@RequestBody Map<String, Object> updates) {  // 只包含需要更新的字段// 部分更新邏輯
    }
    
  5. DELETE 請求:通常使用路徑參數指定要刪除的資源 ID,復雜情況可結合@RequestParam

    java

    @DeleteMapping("/users/{id}")
    public void deleteUser(@PathVariable String id) {// 刪除用戶邏輯
    }
    

遵循這些實踐可以使你的 API 更加直觀和易用,符合 RESTful 設計原則。

五、底層原理探秘

了解@RequestParam@RequestBody的底層原理,不僅能幫助我們更好地理解它們的行為,還能在遇到問題時更快地定位原因。

5.1 SpringMVC 的請求處理流程

SpringMVC 處理一個 HTTP 請求的大致流程如下:

  1. 客戶端發送 HTTP 請求到 DispatcherServlet
  2. DispatcherServlet 根據請求 URL 查找對應的 Handler(通常是控制器方法)
  3. 找到 Handler 后,DispatcherServlet 調用 HandlerAdapter 處理請求
  4. HandlerAdapter 負責解析請求參數,并綁定到 Handler 的方法參數上
  5. 執行 Handler 方法,得到返回結果
  6. HandlerAdapter 將返回結果封裝成 ModelAndView
  7. DispatcherServlet 根據 ModelAndView 選擇合適的 ViewResolver 渲染視圖
  8. 將渲染結果返回給客戶端

@RequestParam@RequestBody的處理發生在第 4 步,由特定的參數解析器完成。

5.2 @RequestParam 的解析原理

@RequestParam的解析主要由RequestParamMethodArgumentResolver類完成,其工作流程如下:

  1. 檢查方法參數是否標注了@RequestParam注解
  2. 如果標注了,從請求對象(HttpServletRequest)中獲取參數值:
    • 首先調用request.getParameter(name)獲取參數值
    • 這方法會同時查找查詢參數和表單參數(application/x-www-form-urlencoded
  3. 如果參數是數組或集合,將獲取到的多個值轉換為對應的數組或集合
  4. 進行類型轉換,將字符串參數轉換為方法參數的類型
  5. 將轉換后的值綁定到方法參數上

request.getParameter(name)方法的底層實現會根據請求的Content-Type不同而有不同的處理邏輯:

  • 對于 GET 請求,從 URL 的查詢字符串中解析參數
  • 對于 POST 請求且Content-Typeapplication/x-www-form-urlencoded,從請求體中解析參數
  • 其他類型的請求,主要從 URL 的查詢字符串中解析參數

5.3 @RequestBody 的解析原理

@RequestBody的解析主要由RequestResponseBodyMethodProcessor類完成,其工作流程如下:

  1. 檢查方法參數是否標注了@RequestBody注解
  2. 從請求對象中獲取輸入流(InputStream)
  3. 根據請求頭中的Content-Type選擇合適的 HttpMessageConverter
  4. 使用選中的 HttpMessageConverter 將請求體內容轉換為方法參數的類型
  5. 將轉換后的對象綁定到方法參數上

HttpMessageConverter 是一個接口,不同的實現類處理不同的數據格式:

  • MappingJackson2HttpMessageConverter:處理 JSON 格式(默認包含)
  • Jaxb2RootElementHttpMessageConverter:處理 XML 格式(需要相應依賴)
  • StringHttpMessageConverter:處理字符串格式
  • FormHttpMessageConverter:處理表單數據

Spring 會根據Content-Type請求頭和目標類型來選擇最合適的 HttpMessageConverter。

5.4 參數解析器的優先級

SpringMVC 中有多種參數解析器,它們有不同的優先級:

  1. RequestParamMethodArgumentResolver(處理@RequestParam
  2. RequestResponseBodyMethodProcessor(處理@RequestBody
  3. 其他解析器(如處理路徑參數的PathVariableMethodArgumentResolver等)

這種優先級意味著在解析參數時,Spring 會先嘗試用@RequestParam的解析器處理,再嘗試用@RequestBody的解析器處理。

但實際上,由于@RequestParam@RequestBody的適用場景不同(一個處理參數,一個處理請求體),它們很少會產生沖突。

六、常見面試題解析

@RequestParam@RequestBody是 Java 面試中經常被問到的知識點,掌握以下常見問題的答案,能讓你在面試中更有優勢。

6.1 @RequestParam 和 @RequestBody 的區別是什么?

這是最基礎也最常被問到的問題,回答時應涵蓋數據位置、適用場景、支持的數據格式等方面:

@RequestParam@RequestBody都是 SpringMVC 中用于獲取請求數據的注解,主要區別如下:

  1. 數據位置不同@RequestParam用于獲取 URL 查詢參數或表單參數,@RequestBody用于獲取請求體中的數據
  2. 適用 HTTP 方法@RequestParam常用于 GET 方法,@RequestBody常用于 POST、PUT 等方法
  3. 數據格式@RequestParam處理鍵值對形式的數據,@RequestBody處理 JSON、XML 等結構化數據
  4. 參數數量@RequestParam可以同時獲取多個參數,@RequestBody通常一次處理一個對象
  5. 使用場景:簡單參數用@RequestParam,復雜對象用@RequestBody

6.2 什么時候用 @RequestParam,什么時候用 @RequestBody?

回答這個問題時,應結合具體場景說明:

選擇使用哪個注解主要取決于參數的類型、位置和復雜度:

  1. 當參數是簡單類型(字符串、數字等)且位于 URL 查詢字符串或表單中時,使用@RequestParam
  2. 當參數是復雜對象(尤其是包含嵌套結構的對象)時,使用@RequestBody
  3. GET 請求通常使用@RequestParam,因為 GET 請求的參數通常在 URL 中
  4. POST、PUT 等請求如果需要傳遞復雜數據,使用@RequestBody
  5. 表單提交(application/x-www-form-urlencoded)使用@RequestParam
  6. JSON 或 XML 格式的數據使用@RequestBody

簡單來說,簡單參數用@RequestParam,復雜對象用@RequestBody;URL 中的參數用@RequestParam,請求體中的數據用@RequestBody

6.3 GET 請求可以用 @RequestBody 嗎?

這個問題考察對 HTTP 規范和 SpringMVC 實現的理解:

從技術上講,SpringMVC 允許在 GET 請求中使用@RequestBody,但這是不推薦的做法,原因如下:

  1. HTTP 規范:GET 請求的主要目的是獲取資源,通常不應該有請求體。雖然 HTTP 規范沒有明確禁止 GET 請求有請求體,但很多服務器和代理對 GET 請求體的支持并不完善。
  2. 實際問題
    • 一些瀏覽器會忽略 GET 請求的請求體
    • 緩存機制通常基于 URL,GET 請求體不會被納入緩存鍵的計算
    • 日志系統通常不會記錄 GET 請求體,不利于問題排查
  3. 設計原則:RESTful 設計原則中,GET 請求用于查詢,參數應該放在 URL 中,這也是@RequestParam的典型應用場景

因此,實際開發中應避免在 GET 請求中使用@RequestBody,堅持使用@RequestParam獲取 GET 請求的參數。

6.4 @RequestParam 的 required 屬性為 true 但參數不存在會怎樣?

這個問題考察對異常處理的理解:

@RequestParamrequired屬性為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 字符串到日期對象的轉換,常用的解決方案有:

  1. 使用 @JsonFormat 注解:在實體類的日期字段上添加注解,指定日期格式

    java

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    
  2. 配置全局日期格式:通過配置 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;
    }
    
  3. 使用自定義序列化器 / 反序列化器:對于特殊的日期格式,可以編寫自定義的序列化器和反序列化器

選擇哪種方案取決于項目需求:單個字段的特殊格式用@JsonFormat,統一的日期格式用全局配置。

七、總結與展望

@RequestParam@RequestBody是 SpringBoot 開發中處理 HTTP 請求參數的核心注解,掌握它們的使用方法和底層原理,對于編寫高質量的 Web 應用至關重要。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/96775.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/96775.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/96775.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Docker】P2 Docker環境構建準備:MacOS 與 Linux

目錄操作系統與 Docker 的兼容性分析Docker 技術本質MacOS 環境下的 Docker 構建1. 安裝前準備2. Docker Desktop安裝3. 鏡像加速配置高級操作&#xff1a;文件共享配置Linux 環境下的 Docker 構建卸載歷史版本配置軟件源Docker 核心組件安裝系統服務配置鏡像加速器配置應用配置…

OpenCV 發票識別全流程:透視變換與輪廓檢測詳解

目錄 前言 一、核心技術原理&#xff1a;透視變換與輪廓檢測 1. 透視變換&#xff1a;讓傾斜發票 “正過來” &#xff08;1&#xff09;什么是透視變換&#xff1f; &#xff08;2&#xff09;透視變換的 5 個關鍵步驟 2. 輪廓檢測&#xff1a;精準定位發票區域 &#x…

并發:使用volatile和不可變性實現線程安全

《Java并發編程實戰》中的VolatileCachedFactorizer展示了如何使用volatile和不可變性來實現線程安全。解決了簡單緩存實現中可能出現的線程安全問題&#xff0c;同時避免了全量同步帶來的性能開銷。 場景背景 假設有一個服務&#xff08;如因數分解服務&#xff09;&#xff0…

Linux x86 stability和coredump

1 POSIX pthread_create原理 1&#xff09;fork()、pthread_create()、vfork()對應的系統調用分別是sys_fork()、sys_clone()、sys_vfork()&#xff0c;它們在內核中都是通過do_fork()實現的。 2&#xff09;系統中所有的進程都組織在init_task.tasks鏈表下面&#xff0c;每個進…

【PyTorch】多對象分割

對象分割任務的目標是找到圖像中目標對象的邊界。實際應用例如自動駕駛汽車和醫學成像分析。這里將使用PyTorch開發一個深度學習模型來完成多對象分割任務。多對象分割的主要目標是自動勾勒出圖像中多個目標對象的邊界。 對象的邊界通常由與圖像大小相同的分割掩碼定義&#xf…

RabbitMQ---面試題

總結我們所學內容&#xff0c;這里推薦博客進行復習 RabbitMQ---面試題_rabbitmq常問面試題-CSDN博客

MasterGo自動布局(Auto Layout)

自動布局是用來表示 子元素與子元素之間互相影響的一種排版方式,是一種響應式布局技術。一般是將所有元素設計完成后再使用自動布局進行設置。 自動布局就是響應式布局,就是在不同尺寸的手機上寬度不同都應該怎么展示。 一般頁面的一級元素使用約束進行相對定位,二級元素及里…

還在重啟應用改 Topic?Spring Boot 動態 Kafka 消費的“終極形態”

場景描述&#xff1a; 你的一個微服務正在穩定地消費 Kafka 的 order_topic。現在&#xff0c;上游系統為了做業務隔離&#xff0c;新增加了一個 order_topic_vip&#xff0c;并開始向其中投遞 VIP 用戶的訂單。你需要在不重啟、不發布新版本的情況下&#xff0c;讓你現有的消費…

使用vllm部署neo4j的text2cypher-gemma-2-9b-it-finetuned-2024v1模型

使用vllm部署neo4j的text2cypher-gemma-2-9b-it-finetuned-2024v1模型 系統環境準備 由于使用的基于 nvcr.io/nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04 的 workbench,需要進行以下準備(其他系統環境可忽略) ldconfig -p | grep libcudnn 找到 libcudnn 的so庫,然…

Coze源碼分析-資源庫-創建知識庫-前端源碼-核心組件

概述 本文深入分析Coze Studio中用戶創建知識庫功能的前端實現。該功能允許用戶在資源庫中創建、編輯和管理知識庫資源&#xff0c;為開發者提供了強大的知識管理和數據處理能力。通過對源碼的詳細解析&#xff0c;我們將了解從資源庫入口到知識庫配置彈窗的完整架構設計、組件…

基于時空數據的網約車訂單需求預測與調度優化

一、引言隨著共享出行行業的蓬勃發展&#xff0c;網約車已成為城市交通的重要組成部分。如何精準預測訂單需求并優化車輛調度&#xff0c;是提升平臺運營效率、改善用戶體驗的關鍵。本文提出一種基于時空數據的網約車訂單需求預測與調度優化方案&#xff0c;通過網格化城市空間…

數據結構 Java對象的比較

在Java中&#xff0c;凡是涉及到比較的&#xff0c;可以分為兩類情況&#xff1a;一類是基本數據類型的比較&#xff0c;另一類是引用數據類型的比較。對于基本數據類型的比較&#xff0c;我們通過關系運算符&#xff08;、>、<、!、>、<&#xff09;進行它們之間的…

企智匯建筑施工項目管理系統:全周期數字化管控,賦能工程企業降本增效!?建筑工程項目管理軟件!建筑工程項目管理系統!建筑項目管理軟件企智匯軟件

在建筑施工行業&#xff0c;項目進度滯后、成本超支、質量安全隱患頻發、多方協同不暢等問題&#xff0c;一直是制約企業發展的痛點。傳統依賴人工記錄、Excel 統計的管理模式&#xff0c;不僅效率低下&#xff0c;更易因信息斷層導致決策失誤。企智匯建筑施工項目管理系統憑借…

k8s-臨時容器學習

臨時容器學習1. 什么是臨時容器2. 實驗1. 什么是臨時容器 在官網&#xff1a;https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/ephemeral-containers/ 中有介紹 臨時容器是用于調試Pod中崩潰的容器或者不具備調試工具&#xff0c;比如在一個運行著業務的容器中&am…

Python 2025:低代碼開發與自動化運維的新紀元

從智能運維到無代碼應用&#xff0c;Python正在重新定義企業級應用開發范式在2025年的企業技術棧中&#xff0c;Python已經從一個"開發工具"演變為業務自動化的核心平臺。根據Gartner 2025年度報告&#xff0c;68%的企業在自動化項目中使用Python作為主要開發語言&am…

Netty 在 API 網關中的應用篇(請求轉發、限流、路由、負載均衡)

Netty 在 API 網關中的應用篇&#xff08;請求轉發、限流、路由、負載均衡&#xff09;隨著微服務架構的普及&#xff0c;API 網關成為服務之間通信和安全控制的核心組件。在構建高性能網關時&#xff0c;Netty 因其高吞吐、低延遲和異步非阻塞 IO 的特性&#xff0c;成為不少開…

基于STM32設計的青少年學習監控系統(華為云IOT)_282

文章目錄 一、前言 1.1 項目介紹 【1】項目開發背景 【2】設計實現的功能 【3】項目硬件模塊組成 【4】設計意義 【5】國內外研究現狀 【6】摘要 1.2 設計思路 1.3 系統功能總結 1.4 開發工具的選擇 【1】設備端開發 【2】上位機開發 1.5 參考文獻 1.6 系統框架圖 1.7 系統原理…

手寫Spring底層機制的實現【初始化IOC容器+依賴注入+BeanPostProcesson機制+AOP】

摘要&#xff1a;建議先看“JAVA----Spring的AOP和動態代理”這個文章&#xff0c;解釋都在代碼中&#xff01;一&#xff1a;提出問題依賴注入1.單例beans.xml<?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframe…

5G NR-NTN協議學習系列:NR-NTN介紹(2)

NTN網絡作為依賴衛星的通信方式&#xff0c;需要面對的通信距離&#xff0c;通信雙方的移動速度都和之前TN網絡存在巨大差異。在距離方面相比蜂窩地面網絡Terrestrial Network通信距離從最小幾百米到最大幾十km的情況&#xff0c;NTN非地面網絡的通信距離即使是近地軌道的LEO衛…

線掃相機采集圖像起始位置不正確原因總結

1、幀觸發開始時間問題 問題描述: 由于幀觸發決定了線掃相機的開始采集圖像位置,比如正確的位置是A點開始采集,結果你從B點開始觸發幀信號,這樣出來的圖像起始位置就不對 解決手段: 軟件需要記錄幀觸發時軸的位置 1)控制卡控制軸 一般使用位置比較觸發,我們可以通過監…