当前位置: 首页 > news >正文

Spring MVC 常用注解及代码示例

Spring MVC 常用注解及代码示例

    • @Controller 与RestController
      • 结论:
      • 对比表格
      • 代码示例深度解析
        • 示例 1:使用 `@Controller`(返回视图)
        • 示例 2:使用 `@Controller` + `@ResponseBody`(返回数据)
        • 示例 3:使用 `@RestController`(返回数据,推荐方式)
        • @ResponseBody注解
          • 核心功能
    • @RequestMapping - 定义请求的入口
      • 注解可用在哪些位置?
          • 如何设置your-app?
      • 核心属性
          • 属性示例代码:
          • 构造请求
          • 返回结果
      • 参数路径通配符
        • 代码示例
      • 重要注意事项和最佳实践
    • @RequestMapping:处理参数的相关注解 - 获取请求中的数据
      • 1. `@RequestParam` - 获取查询参数或表单数据
          • 示例一:GET请求URL中携带参数
          • 示例二:POST请求参数在请求体
      • 2. `@PathVariable` - 获取 URL 路径中的变量
        • 高级通配:使用正则表达式进行约束和匹配
          • 代码示例与讲解
          • 正则表达式部分说明:
          • 通配符 (`*`, `**`) 结合使用
          • 重要注意事项
      • 3. `@RequestBody` - 获取请求体(如 JSON)
          • @JsonAlias:若传递的JSON 字段名与 Java 字段名不一致
          • @JsonProperty:谨慎使用与存在的问题
          • @JsonAlias 与 @JsonProperty 的序列化差异
      • 4. `@ModelAttribute` - 获取模型数据(常用于表单对象)
          • 示例一:GET请求URL携带参数
          • 示例二:POST请求body携带参数
          • 示例三:省略@ModelAttribute注解
          • 简写方式与加`@ModelAttribute`注解方式有什么区别?
          • 显式添加 @ModelAttribute 的理由是什么
      • 5. @RequestHeader - 获取HTTP请求头的值
          • 用法示例
      • 6. @CookieValue - 获取Cookie的值
          • 示例一: 获取cookie中的属性值
          • 示例二:获取cookie对象
      • 7. @RequestPart - 获取文件上传部分信息
          • 示例一:多文件上传与form表单提交
          • 示例二:多文件上传与JSON传输
      • 8. HttpEntity封装请求原始数据
          • 如何使用 HttpEntity
          • 什么时候使用 HttpEntity?
          • HttpEntity 的特殊化子类
          • 实际应用示例
            • 示例 1:处理文件上传并返回元数据
            • 示例 2:代理请求到另一个服务
            • 示例3:文件下载示例
            • 示例4:文件下载标准格式示例(推荐)
          • 总结
      • 9. 原生API
    • 总结与最佳实践
      • @RequestParam、@RequestBody以及@ModelAttribute的区别和异同?
        • 核心区别对比表
        • 详细解释与示例
        • 1. @RequestParam:获取单个参数
        • 2. @ModelAttribute:绑定到对象(表单处理)
        • 3. @RequestBody:解析请求体(API开发)
        • 关键异同总结
        • 如何选择?
      • @RequestHeader, @CookieValue, @RequestPart对比总结

@Controller 与RestController

@Controller@RestController 是 Spring MVC 中用于定义控制器的两个核心注解。

  • 随着前后端分离架构的普及,@RestController 的使用频率远远超过了传统的 @Controller。大部分新一代的 Java Web 项目都是后端只提供 API 接口,因此 @RestController 成为了事实上的标准选择。
  • 要页面,用 @Controller;要数据,用 @RestController

结论:

如果开发...

  • 传统多页面网站(MPA):使用 @Controller。你需要视图解析器来渲染 HTML 页面,并与模型(Model)交互。
  • RESTful API / 前后端分离项目(SPA):使用 @RestController。你只负责提供数据(JSON/XML),前端框架(如 React, Vue, Angular)负责渲染页面。

@RestController 是一个组合注解,它等同于 @Controller + @ResponseBody 它们的根本区别在于如何处置方法的返回值

  • @Controller:适用于传统 Web 应用,方法返回值通常代表一个视图名(View Name),由视图解析器渲染后返回 HTML 页面。
  • @RestController:专为 RESTful Web 服务设计,方法的返回值会直接写入 HTTP 响应体(Response Body),通常转换为 JSON/XML 数据。

下面我们通过一个对比表格和代码示例来详细解析。


对比表格

特性@Controller@RestController
本质一个标准的 Spring MVC 控制器注解一个组合注解,包含 @Controller@ResponseBody
返回值处理返回值通常被解析为视图名(View Name)返回值直接写入 HTTP 响应体
用途构建传统的、服务端渲染的 Web 应用(返回 HTML、JSP、Thymeleaf 模板等)构建 RESTful API,提供 JSON/XML 数据(前后端分离架构)
视图技术需要配合视图解析器(ViewResolver)如 Thymeleaf、FreeMarker、JSP不需要视图解析器
注解要求如果需要返回数据而非视图,方法上需额外添加 @ResponseBody无需额外添加 @ResponseBody,类级别已默认拥有
HTTP 响应头Content-Type 通常为 text/htmlContent-Type 通常为 application/json

代码示例深度解析

示例 1:使用 @Controller(返回视图)
@Controller // 表明这是一个传统的MVC控制器
@RequestMapping("/traditional")
public class TraditionalController {@GetMapping("/user")public String getUserPage(Model model) {// 1. 业务逻辑:向模型中添加数据model.addAttribute("user", new User("Alice", 25));// 2. 返回一个字符串,这个字符串会被视图解析器解析为具体的页面return "user-detail"; // 视图解析器会查找 'user-detail.html' 或 'user-detail.jsp' 模板}
}

工作流程:

  1. 浏览器访问 GET /traditional/user
  2. 方法执行,将 User 对象放入 Model
  3. 返回字符串 "user-detail"
  4. DispatcherServlet 将此字符串交给 ViewResolver(视图解析器)。
  5. ViewResolver 根据前缀后缀配置(例如:前缀 classpath:/templates/,后缀 .html)找到物理视图:classpath:/templates/user-detail.html。(resources/static/index.html)
  6. 视图模板(如 Thymeleaf)引擎接管,将模型数据与 HTML 模板合并渲染。
  7. 最终生成一个完整的 HTML 页面返回给浏览器。

结果: 浏览器收到并显示一个渲染好的 HTML 页面。


示例 2:使用 @Controller + @ResponseBody(返回数据)
@Controller // 仍然是传统控制器
@RequestMapping("/hybrid")
public class HybridController {@GetMapping("/user")@ResponseBody // 关键注解:告诉Spring,返回值直接写入响应体,不要找视图public User getUserData() {// 直接返回一个对象return new User("Alice", 25);}
}

工作流程:

  1. 浏览器或客户端访问 GET /hybrid/user
  2. 方法执行,返回 User 对象。
  3. 因为方法上有 @ResponseBodyDispatcherServlet 会跳过视图解析。
  4. 根据请求的 Accept 头,选择合适的 HttpMessageConverter(如 MappingJackson2HttpMessageConverter)。
  5. 转换器将 User 对象序列化为 JSON 字符串
  6. JSON 数据被直接写入 HTTP 响应体

结果: 客户端收到一段 JSON 数据{"name": "Alice", "age": 25}


示例 3:使用 @RestController(返回数据,推荐方式)
@RestController // 等价于 @Controller + @ResponseBody
@RequestMapping("/api")
public class RestApiController {@GetMapping("/user")// 不需要再写 @ResponseBody!public User getUser() {return new User("Alice", 25);}@PostMapping("/user")public User createUser(@RequestBody User user) {// ... 保存用户的逻辑return savedUser;}
}

工作流程:
与示例 2 完全相同。因为 @RestController 已经在类级别隐式地为所有方法添加了 @ResponseBody 语义。

结果: 客户端访问 GET /api/user 会收到 JSON 数据{"name": "Alice", "age": 25}


@ResponseBody注解
  • @ResponseBody注解的核心作用是指定一个控制器方法的返回值应当直接写入 HTTP 响应体(Response Body)中,而不是被当作一个视图名称(View Name)由视图解析器(View Resolver)去查找并渲染一个页面。

  • 它的主要用途是实现 RESTful Web 服务前后端分离架构,在这种架构下,后端通常返回的是数据(如 JSON、XML),而不是一个完整的 HTML 页面。

  • 返回 JSONXML其他数据格式非 HTML 页面

核心功能
  • 直接写入响应体:将方法返回值直接写入 HTTP 响应流中
  • 绕过视图解析:不经过视图解析器(ViewResolver)处理
  • 自动内容协商:根据客户端请求的 Accept 头自动选择适当的消息转换器

@RequestMapping - 定义请求的入口

@RequestMapping 是 Spring MVC 中最基本的注解,用于将 Web 请求映射到特定的控制器类或方法上。它定义了“哪个请求”应该由“哪个方法”来处理。

注解可用在哪些位置?

  • 级别:提供初步的请求映射,通常表示共享的路径(模块路径)。
  • 方法级别:进一步细化映射,与类级别注解组合形成完整的 URL 映射。

示例:

@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/profile") // 方法级别:组合成 /users/profilepublic String showProfile() {return "profile";}
}

访问 http://localhost:8080/your-app/users/profile 会触发 showProfile() 方法。

如何设置your-app?
  • your-app 是一个占位符,它代表你的 Web 应用程序的上下文根(Context Root)。它不是由 Spring MVC 的 @RequestMapping 注解直接设置的,而是在部署应用时由 Servlet 容器(如 Tomcat、Jetty)或通过 Spring Boot 的配置来决定的。

简单来说,访问的完整 URL 结构是:
http://<server>:<port>/<context-path>/<spring-mvc-mapping>

  • Spring Boot 内嵌了 Servlet 容器(默认是 Tomcat),设置 Context Path 非常简单,只需要在配置文件 application.propertiesapplication.yml 中修改一个属性即可。
server.servlet.context-path=/mvc
  • 效果:配置完成后,重启应用。你之前定义的 @RequestMapping("/users/profile") 的完整访问路径就变成了:
    http://localhost:8080/mvc/users/profile

  • 如果你不进行任何配置,Spring Boot 默认的 context-path 是 /(空根路径)。这意味着你的访问路径直接就是:
    http://localhost:8080/users/profile

核心属性

@RequestMapping 是一个多功能注解,通过指定其属性可以精确匹配请求。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
@Reflective({ControllerMappingReflectiveProcessor.class})
public @interface RequestMapping {String name() default "";// 访问路径,别名为path@AliasFor("path")String[] value() default {};// 访问路径,别名为value@AliasFor("value")String[] path() default {};// 指定请求方式为:get、post等,才会响应RequestMethod[] method() default {};// 必须携带指定的参数,才能响应。例:?username=zhangsanString[] params() default {};// 必须携带指定的http请求头信息,才能响应。String[] headers() default {};// 必须携带指定的媒体类型Context-Type,才会响应。String[] consumes() default {};// 告诉前端,我返回的媒体类型是json还是其他。String[] produces() default {};
}
属性说明示例
value / path指定请求的 URL 路径(最主要的属性)。支持 Ant 风格(如 ?, *, **)。@RequestMapping("/user/list")
@RequestMapping("/view/*.html")
method指定请求的 HTTP 方法(GET, POST, PUT, DELETE 等)。@RequestMapping(path = "/save", method = RequestMethod.POST)
params要求请求必须包含某些参数,或参数值必须等于特定值。@RequestMapping(path = "/get", params = "type=admin")
(只匹配 /get?type=admin
headers要求请求必须包含某些 HTTP 头@RequestMapping(path = "/data", headers = "content-type=application/json")
consumes指定处理请求的媒体类型(Content-Type)。请求头 Content-Type 必须匹配。@RequestMapping(consumes = "application/json") (只处理请求体为 JSON 的请求)
produces指定响应的媒体类型。告诉客户端我将返回什么类型的数据,并设置 Content-Type 响应头。@RequestMapping(produces = "application/json") (我返回 JSON,并设置响应头)

属性示例代码:
package com.ssg.web.controller;@RestController
@RequestMapping("/user")
public class UserController {/*** 请求路径:/userinfo/{id}* 请求方式(必须为):GET OR POST* 参数信息(必须携带):username=man* 媒体类型(只处理):application/* [json也包括在内]* 请求头信息(必须携带):web-header=hahaha* 响应的媒体信息(返回给前端):application/json* @param id 用户id* @return User对象*/@RequestMapping(path = "/userinfo/{id}", method = {RequestMethod.GET, RequestMethod.POST}, params = {"username=man"},consumes = {"application/json"}, headers = {"web-header=hahaha"}, produces = {MediaType.APPLICATION_JSON_VALUE})public User userInfo(@PathVariable Integer id){return new User(id,"张三","123","admin@web.com","123");}
}
构造请求
GET http://localhost:8080/mvc/user/userinfo/1?username=man
web-header: hahaha
Accept: application/json
Content-Type: application/json
  • 服务器看到 Content-Type: application/json:Spring MVC 会使用 HttpMessageConverter 中配置的 JSON 转换器(如 Jackson)来解析(反序列化) 请求体,并将其转换为一个 Java 对象(如 User 对象),方便你在 @RequestBody User user 参数中使用。

  • 服务器看到 Accept: application/json:Spring MVC 会决定使用能够输出 JSON 的 HttpMessageConverter 来序列化方法的返回值,并设置响应头的 Content-Type。

  • Accept: application/json:这表示您的控制器方法只会生成 application/json 类型的响应。因此,只有当客户端在 Accept 头中明确表示它可以接受 JSON 响应时,Spring 才会调用这个方法。

返回结果
HTTP/1.1 200 
Content-Type: application/json
Date: Mon, 08 Sep 2025 05:28:07 GMT
Connection: close
Content-Length: 79{"id": 1,"name": "张三","password": "123","email": "admin@web.com","phone": "123"
}

参数路径通配符

通配符说明示例匹配的示例不匹配的示例
?匹配单个任意字符/user?/user1, /userA/user, /user12
*匹配任意数量的任意字符,但在单级路径/user/*/user/, /user/1, /user/abc/user/1/profile
**匹配任意数量的任意字符,可以跨越多级路径/user/**/user/, /user/1, /user/1/profile/pic(无,全部匹配)
{name}路径变量(Path Variable),匹配一个路径段并将其捕获为变量/user/{userId}/user/123 -> userId=123/user/
代码示例
@RestController
@RequestMapping("/api")
public class MyController {// 匹配 /api/file1, /api/fileA,但不匹配 /api/file, /api/file123@GetMapping("/file?")public String getSingleFile() {return "Match a single character";}// 匹配 /api/category/books, /api/category/123// 但不匹配 /api/category/books/author (因为 * 是单级的)@GetMapping("/category/*")public String getByCategory() {return "Match one path segment";}// 匹配 /api/search/xxx, /api/search/xxx/yyy/zzz, /api/search/// ** 可以匹配零到多级路径@GetMapping("/search/**")public String searchEverything() {return "Match all paths under search";}// 组合使用:匹配 /api/users/123, /api/users/john.doe 等// 并使用 @PathVariable 注解获取值@GetMapping("/users/{userId}")public String getUserById(@PathVariable String userId) {return "User ID: " + userId;}// 更复杂的组合:匹配 /api/products/clothing/shirts/123// category = "clothing", type = "shirts", id = "123"@GetMapping("/products/{category}/{type}/{id}")public String getProduct(@PathVariable String category,@PathVariable String type,@PathVariable Long id) {return String.format("Category: %s, Type: %s, ID: %d", category, type, id);}// 使用通配符和路径变量混合匹配// 匹配 /api/download/user/avatar/somefile.txt// fileId 将会是 "somefile.txt"@GetMapping("/download/**/{fileId}")public String downloadFile(@PathVariable String fileId) {// 可以通过其他方式解析 ** 匹配的完整路径return "Downloading file: " + fileId;}
}

重要注意事项和最佳实践

  1. 匹配优先级:Spring 会优先匹配最具体的模式。

    • 一个固定的模式(如 /users/active)比一个通配符模式(如 /users/*)更具体。
    • 当多个模式都可能匹配同一个请求时,最具体的模式会被选中。
  2. /**/**/ 通常用作默认映射或捕获所有请求,但要谨慎使用,因为它可能会匹配到你意想不到的请求(如静态资源、Spring Boot 的 actuator 端点等)。

  3. 路径变量 vs 请求参数

    • 路径变量 ({id}):用于标识资源本身(如 /users/1)。
    • 请求参数 (?key=value):用于过滤、排序、分页等(如 /users?role=admin)。通过 @RequestParam 获取。
  4. ** 的用法** 非常强大,常用于实现静态资源映射、统一的异常处理或者某些需要捕获“所有剩余路径”的场景。

场景推荐方式
简单、清晰的层级式路由(RESTful 风格)路径变量 (如 /users/{id})
匹配单级目录下的任意资源* (如 /images/*)
匹配多级嵌套目录下的任意资源** (如 /resources/**)
对路径变量的格式有严格要求(如必须是数字)正则表达式 (如 {id:\\d+})

希望这个详细的解释和示例能帮助你更好地理解和使用 @RequestMapping 中的参数通配符!

@RequestMapping:处理参数的相关注解 - 获取请求中的数据

在控制器方法中,我们需要方便地获取 HTTP 请求中携带的各种数据。Spring MVC 提供了一系列强大的注解来实现自动的参数绑定

1. @RequestParam - 获取查询参数或表单数据

用于获取 URL 查询字符串(Query String)POST 表单提交的数据。

  • 格式URL中?key=9QB9IDyj&value=qxfWeCjG请求体中application/x-www-form-urlencoded 表单。
    • 无论请求参数带到了请求体中或者url?后面,他们都是请求参数,都能进行解析。
  • 默认要求:参数默认是必需的。如果请求中没有该参数,会抛出异常。
  • 注解常用属性
    • value / name: 指定参数名。
    • required: 是否为必选参数,默认为 true。设为 false 时,如果请求中没有此参数,参数值会被设为 null
    • defaultValue: 默认值。如果提供了默认值,required 会自动被设置为 false
示例一:GET请求URL中携带参数
  • 请求url:http://localhost:8080/mvc/user/requestParam?key=9QB9IDyj&value=qxfWeCjG
  • HTTP请求报文:
GET /mvc/user/requestParam2?username=11111&password=2222 HTTP/1.1
Host: localhost:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.50 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Connection: close
  • 示例代码:
    /*** GET请求,将字段key/value用RequestParam进行映射为:username/password* {@code http://localhost:8080/mvc/user/requestParam?key=9QB9IDyj&value=qxfWeCjG}* @return User*/@RequestMapping(value = "/requestParam", method = RequestMethod.GET)public User requestParam(@RequestParam("key") String username, @RequestParam("value")String password ){return new User(1,username,password,"admin@web.com","123");}
  • HTTP响应报文
HTTP/1.1 200 
Content-Type: application/json
Date: Sun, 14 Sep 2025 07:49:19 GMT
Connection: close
Content-Length: 107{"id":1,"name":"11111","password":"2222","email":"admin@web.com","phone":"123","country-address":"address"}

简写形式:如果方法参数名与请求参数名相同,可以省略 @RequestParam("name") 中的值。

  • @RequestParam 会自动绑定参数:?username=7ZyJwVYe&password=oLDeqerM
    /*** GET请求,将key字段用RequestParam进行映射为:k.* {@code http://localhost:8080/mvc/user/requestParam2?username=7ZyJwVYe&password=oLDeqerM}* @return User*/@RequestMapping(value = "/requestParam2", method = RequestMethod.GET)public User requestParam2(@RequestParam String username, @RequestParam String password ){return new User(1,username,password,"admin@web.com","123");}
示例二:POST请求参数在请求体
  • HTTP请求报文
POST /mvc/user/requestParam3 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.50 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 35username=7ZyJwVYe&password=oLDeqerM
  • 示例代码:

在@RequestMapping注解中,我规定必须使用表单提交,否则无法请求该控制方法。这是因为若要@RequestParam接受POST请求,必须使用from表达提交的方式。

    /*** POST请求,请求体中携带参数信息* {@code http://localhost:8080/mvc/user/requestParam2}* {@code 请求体中:?username=7ZyJwVYe&password=oLDeqerM}* 要求:POST请求,必须为form表单提交* @return User*/@RequestMapping(value = "/requestParam3", method = RequestMethod.POST,consumes = APPLICATION_FORM_URLENCODED_VALUE)public User requestParam3(@RequestParam String username, @RequestParam String password) {return new User(1, username, password, "admin@web.com", "123","address");}
  • HTTP响应报文
HTTP/1.1 200 
Content-Type: application/json
Date: Sun, 14 Sep 2025 07:04:11 GMT
Connection: close
Content-Length: 114{"id":1,"name":"7ZyJwVYe","password":"oLDeqerM","email":"admin@web.com","phone":"123","country-address":"address"}

2. @PathVariable - 获取 URL 路径中的变量

用于从 RESTful 风格的 URL 路径中获取变量值。

  • 来源:URL 模板变量,如 /user/{id} 中的 {id}
  • 属性value / name (指定路径变量名,如果方法参数名一致可省略)。
  • 要求:默认是必需的。

示例:

    /*** {@code http://localhost:8080/mvc/user/path-variable/username-111/get/password-222}* @param username username-111* @param password password-222* @return*/@RequestMapping(value = "/path-variable/{username}/get/{password}", method = RequestMethod.GET)public User pathVariable(@PathVariable("username") String username, @PathVariable("password") String password){return new User(1,username,password,"admin@web.com","123");}

其他写法:
在这里插入图片描述

高级通配:使用正则表达式进行约束和匹配

这是 @PathVariable 真正强大之处。你可以在变量名后使用冒号 : 添加一个正则表达式来精确控制变量的匹配模式。

语法: {variableName:regex}

代码示例与讲解

a) 确保参数格式正确(数据验证)

// 匹配 /products/123, 但不匹配 /products/abc
// 使用 \\d+ 确保 id 必须是一个或多个数字
@GetMapping("/products/{id:\\d+}")
public String getProductById(@PathVariable Long id) {return "Product ID (numeric only): " + id;
}// 匹配 /articles/2024-05-27, 但不匹配 /articles/20240527
// 使用正则表达式确保 date 格式为 YYYY-MM-DD
@GetMapping("/articles/{date:\\d{4}-\\d{2}-\\d{2}}")
public String getArticleByDate(@PathVariable String date) {return "Article date: " + date;
}

b) 匹配复杂模式和多个片段

// 匹配 /files/2024/document_123.pdf
// 使用正则表达式分别定义 year 和 filename 的格式
// filename 的正则 [a-zA-Z0-9._-]+ 允许字母、数字、点、下划线、短横线
@GetMapping("/files/{year:\\d{4}}/{filename:[a-zA-Z0-9._-]+}")
public String getFile(@PathVariable int year, @PathVariable String filename) {return "Year: " + year + ", File: " + filename;
}// 一个路径变量捕获多个路径段 (使用 ** 通配符后再捕获)
// 匹配 /download/user/profile/picture.jpg
// filePath 变量将捕获 "user/profile/picture.jpg"
@GetMapping("/download/{filePath:.+}")
public String downloadFile(@PathVariable String filePath) {return "Downloading file from path: " + filePath;
}// 更复杂的例子:匹配语义化版本号
// 匹配 /api/version/v1.2.3-beta
@GetMapping("/api/version/v{major:\\d+}\\.{minor:\\d+}\\.{patch:\\d+}(-{qualifier:.*})?")
public String getVersion(@PathVariable String major,@PathVariable String minor,@PathVariable String patch,@PathVariable(required = false) String qualifier) { // qualifier 是可选的String version = major + "." + minor + "." + patch;if (qualifier != null) {version += "-" + qualifier;}return "Version: " + version;
}
正则表达式部分说明:
  • \\d+:一个或多个数字。
  • \\d{4}:正好 4 位数字。
  • [a-zA-Z0-9._-]+:一个或多个字母、数字、点、下划线或短横线。
  • .+:一个或多个任意字符(包括斜杠,但需要注意转义和匹配规则)。
  • (-{qualifier:.*})?(- 开始一个分组,匹配短横线。{qualifier:.*}) 捕获名为 qualifier 的变量,其值为任何字符(零个或多个)。最后的 )? 使整个分组可选。

通配符 (*, **) 结合使用

@PathVariable 常与 Ant 风格通配符一起使用,以实现更灵活的路径匹配,尤其是在你不知道后面有多少级路径时。

// 使用 ** 匹配多级路径,然后通过 @PathVariable 捕获最后一部分
// 匹配 /api/static/images/users/avatar.jpg
// fileId 将是 "avatar.jpg"
@GetMapping("/api/static/**/{fileId}")
public String serveStaticFile(@PathVariable String fileId) {// 如果需要,可以通过 HttpServletRequest 获取完整的请求路径来解析 ** 部分return "Serving file: " + fileId;
}// 匹配所有以 /admin/ 开头的路径,并捕获剩余部分
// 例如 /admin/user/create, /admin/settings/general
// 然后根据 capturedPath 进行路由分发或处理
@GetMapping("/admin/{capturedPath:**}")
public String adminRouter(@PathVariable String capturedPath) {return "Admin path: " + capturedPath;
}
重要注意事项
  1. 冲突与优先级:当多个 @RequestMapping 模式都可以匹配同一个请求时,Spring 会选择最具体的那个。一个没有通配符的路径比一个有通配符的路径更具体。例如,/users/active 会优先于 /users/{id} 匹配 /users/active 这个请求。

  2. .(点号)的特殊处理:默认情况下,Spring 会将路径变量中的点号 (.) 后面的内容截断。例如,对于路径 /files/my.file.txt 和模式 /files/{name}name 变量得到的值默认是 my.file。要获取完整值,有两种方式:

    • 在模式中使用正则表达式/files/{name:.+}
    • 在配置中修改spring.mvc.pathmatch.use-suffix-pattern=false (在 Spring Boot 新版本中默认已是 false)。
  3. 可选路径变量:Spring MVC 本身不支持直接在 URI 模板中声明可选路径变量(如 /api/{optionalVar?})。实现可选功能通常有两种方法:

    • 提供多个映射方法:为有该变量和没有该变量的情况分别编写方法。
    • @PathVariablerequired 属性设为 false,但这要求你同时修改模式,使其能够匹配两种情况(通常很难用一条正则实现)。更常见的做法是使用请求参数 (@RequestParam(required = false)) 来实现可选功能。

@PathVariable 的强大通配能力主要来自于与其结合使用的 URI 模式表达式

需求场景推荐方式示例
基本捕获{variableName}/users/{id}
格式验证 (数字、日期等){variableName:regex}/products/{id:\\d+}
捕获包含特殊字符(如点号)的值{variableName:.+}/files/{name:.+}
捕获未知的多级路径结合 ** 通配符/api/**/{finalSegment}
实现近似“可选”变量提供多个 @RequestMapping 或使用请求参数/api/books/api/books/{category}

掌握这些组合用法,你可以设计出非常灵活且强大的 RESTful API 端点。

3. @RequestBody - 获取请求体(如 JSON)

用于将 HTTP 请求体(如 JSON、XML 数据)自动转换并绑定到 Java 对象上。

  • 来源:请求体(Request Body),通常是 application/jsonapplication/xml
  • 工作原理:依靠 HttpMessageConverter(如 Jackson 的 MappingJackson2HttpMessageConverter)来完成反序列化。
  • 常见用法:接收前端通过 AJAX 发送的 JSON 数据。
  • 其他:如果用POJO对象的方式接受参数,可以不写@RequestBody

示例:

  • post请求传递json数据
{"id": 12,"name": "cDyIr0ca","password": "V0h3cU8r","email": "admin@web.com","phone": "123","country-address": "china"
}
  • 示例代码
    /*** 测试@RequestBody,将json字符串转为对象* @param user {  "id": 12,  "name": "cDyIr0ca",  "password": "V0h3cU8r",  "email": "admin@web.com",  "phone": "123"}* @return User对象*/@RequestMapping(value = "/request-body", method = RequestMethod.POST)public User requestBody(@RequestBody User user) {User u = new User();BeanUtils.copyProperties(user, u);System.out.println("u = " + u);return u;}
@JsonAlias:若传递的JSON 字段名与 Java 字段名不一致
  • 可以使用@JsonAlias注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private Integer id;private String name;private String password;private String email;private String phone;
//    @JsonProperty("country-address")@JsonAlias("country-address")private String address;
}

这样配置后:

  • 请求中的 country-address 字段会被正确映射到 address 属性
  • 响应中的 address 属性会被序列化为 address 字段
@JsonProperty:谨慎使用与存在的问题
  • 若使用@JsonProperty注解,则会出现修改Bean属性的名称。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private Integer id;private String name;private String password;private String email;private String phone;@JsonProperty("country-address")private String address;
}
  • 返回的Json字符串为:address字段被country-address替代。
{"id": 12,"name": "cDyIr0ca","password": "V0h3cU8r","email": "admin@web.com","phone": "123","country-address": "china"
}

问题原因@JsonProperty注解在 Jackson 中有双重作用:

  • 反序列化(JSON → Java 对象):将 JSON 中的字段名映射到 Java 属性
  • 序列化(Java 对象 → JSON):将 Java 属性映射到 JSON 中的字段名

当您在字段上使用 @JsonProperty("country-address") 时,您告诉 Jackson:

  • 在反序列化时,将 JSON 中的 country-address 字段映射到 Java 的 address 属性

  • 在序列化时,将 Java 的 address 属性序列化为 JSON 中的 country-address 字段

这就是为什么返回的 JSON 中包含 country-address 而不是 address。

@JsonAlias 与 @JsonProperty 的序列化差异

@JsonAlias 仅影响反序列化(JSON → Java对象),而 @JsonProperty 同时影响序列化反序列化。如果你在字段上同时使用了 @JsonProperty 和 @JsonAlias,例如:

@JsonProperty("serializedName") // 序列化时会使用 "serializedName"
@JsonAlias({"alias1", "alias2"}) // 反序列化时接受 "alias1" 或 "alias2"
private String myField;

那么:

  • 序列化(Java对象 → JSON):该字段会使用 @JsonProperty 指定的名称(例如 “serializedName”)。
  • 反序列化(JSON → Java对象):JSON 数据中的 “alias1”、“alias2” 或 “serializedName” 都会被映射到 myField 上。

4. @ModelAttribute - 获取模型数据(常用于表单对象)

介绍:当一个控制器方法的参数是一个 POJO(如您的 User 类),并且没有使用任何特定注解(如 @RequestBody)时,Spring 默认会使用 @ModelAttribute 的逻辑来处理它。

限制条件:

  • 它不会去主动解析请求体Body),而是从URL查询参数(Query String)表单数据(Form Data,application/x-www-form-urlencoded) 中获取数据。
    • Post请求也可以,但是传递参数需要拼接在URL中或请求体中。

功能强大,可用于:

  1. 方法参数绑定:将请求参数绑定到一个复合对象(如表单对象、DTO)。
  2. 方法级别注解:在方法调用前,自动将对象加入模型(Model)。

作为参数注解(常用)

  • 它会把请求参数按名称匹配的方式绑定到对象的属性中。
  • 非常适合处理表单提交,可以一次性获取所有相关字段。
示例一:GET请求URL携带参数
  • HTTP请求报文
GET /mvc/user/model?id=1&name=222&password=333 HTTP/1.1
User-Agent: PostmanRuntime/7.46.0
Accept: */*
Postman-Token: 07ef9d02-dc20-450e-9915-547e0d85fa4c
Host: localhost:8080
Accept-Encoding: gzip, deflate
Connection: close
  • 示例代码:
    /*** GET请求,URL携带请求参数* @param user ?id=1&name=222&password=333* @return user*/@RequestMapping(value = "/model", method = RequestMethod.GET)public User model(@ModelAttribute User user) {User u = new User();BeanUtils.copyProperties(user, u);System.out.println("u = " + u);return u;}
  • HTTP响应报文
HTTP/1.1 200 
Content-Type: application/json
Date: Sun, 14 Sep 2025 08:17:58 GMT
Connection: close
Content-Length: 79{"id":1,"name":"222","password":"333","email":null,"phone":null,"address":null}
示例二:POST请求body携带参数
  1. POST请求体中携带报文。
  2. POST url连接中携带报文
  • HTTP请求报文
POST /mvc/user/model2 HTTP/1.1
User-Agent: PostmanRuntime/7.46.0
Accept: */*
Host: localhost:8080
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 26
Content-Type: application/x-www-form-urlencodedid=1&name=222&password=333
  • 示例代码
   @RequestMapping(value = "/model2", method = RequestMethod.POST)public User model2(@ModelAttribute User user) {User u = new User();BeanUtils.copyProperties(user, u);System.out.println("u = " + u);return u;}
  • HTTP响应报文
HTTP/1.1 200 
Content-Type: application/json
Date: Sun, 14 Sep 2025 08:25:37 GMT
Connection: close
Content-Length: 79{"id":1,"name":"222","password":"333","email":null,"phone":null,"address":null}
示例三:省略@ModelAttribute注解
  • HTTP请求报文
POST /mvc/user/model3?id=1&name=222&password=333 HTTP/1.1
User-Agent: PostmanRuntime/7.46.0
Accept: */*
Postman-Token: 4fc46231-1874-4597-97f5-8148c440e45f
Host: localhost:8080
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 0
  • 示例代码
   @RequestMapping(value = "/model3", method = RequestMethod.POST)public User model3(User user) {User u = new User();BeanUtils.copyProperties(user, u);System.out.println("u = " + u);return u;}
  • HTTP响应报文
HTTP/1.1 200 
Content-Type: application/json
Date: Sun, 14 Sep 2025 08:30:40 GMT
Connection: close
Content-Length: 79{"id":1,"name":"222","password":"333","email":null,"phone":null,"address":null}
简写方式与加@ModelAttribute注解方式有什么区别?

这是一个很好的问题,它触及了Spring MVC的一个核心便利特性:隐式规则。

简单直接的回答是:在这个特定场景下,不加 @ModelAttribute 注解与加上该注解,效果是完全一样的。

原因解析:Spring MVC的默认规则

  • Spring MVC的设计哲学之一是“约定优于配置”(Convention over Configuration)。为了简化开发,它设定了一系列默认规则:
  1. 简单类型参数(如 String, int, Integer):默认使用 @RequestParam 来解析。
  2. 复杂对象参数(如 User, Order自定义POJO):默认使用 @ModelAttribute 来解析。

所以,当你写 public User model3(User user) 时,Spring看到 User 是一个复杂类型,它会自动地、隐式地为其应用@ModelAttribute的绑定逻辑。这个过程对你来说是透明的。

  • 调用 User 类的默认无参构造函数来创建对象实例。
  • 遍历请求中的所有参数(如 username, password, email)。
  • 查找 User 类中是否有与之匹配的setter方法(如 setUsername(String name))。
  • 找到后,进行类型转换(如果需要)并调用setter方法将请求参数的值注入到对象中。

所以,public User model3(User user)public User model3(@ModelAttribute User user) 在功能上是完全等效的。

显式添加 @ModelAttribute 的理由是什么

既然效果一样,为什么我们有时还会看到代码中显式地写上 @ModelAttribute 呢?

代码清晰性与可读性(最主要的原因):

  • 显式注解:明确地告诉了代码的阅读者(包括未来的你自己或其他开发者),这个参数是通过模型属性绑定的,其数据来自HTTP请求并会被填充到一个对象中。这是一种“自文档化”的代码实践。
  • 隐式规则:依赖框架的默认行为。对于不熟悉Spring MVC默认约定的开发者来说,可能需要停下来思考一下 User user 这个参数是怎么被注入的。

显式地加上 @ModelAttribute 通常是更推荐的做法。它使代码的意图更加清晰,减少了误解的可能性,遵循了“明确优于隐晦”的编程原则。这对于团队协作和长期代码维护非常有益。

5. @RequestHeader - 获取HTTP请求头的值

作用@RequestHeader 注解用于将控制器方法参数绑定到HTTP请求头的值。

使用场景
当需要获取请求中的特定头部信息时使用,例如:

  • 获取客户端信息(User-Agent)
  • 获取认证令牌(Authorization)
  • 获取内容类型(Content-Type)
  • 获取语言偏好(Accept-Language)
用法示例
    /*** 获取HTTP 请求头信息* @param user POJO 对象* @param userAgent 浏览器信息* @param host 地址,如果不存在设置默认值* @param headers Map对象* @param authorization 令牌,不需要必填* @return user*/@RequestMapping(value = "/request-header", method = RequestMethod.GET)public User requestHeader(User user,@RequestHeader("User-Agent") String userAgent,@RequestHeader(value = "host", defaultValue = "127.0.0.1") String host,@RequestHeader Map<String, String> headers,@RequestHeader(value = "Authorization", required = false) String authorization) {User u = new User();BeanUtils.copyProperties(user, u);System.out.println("u = " + u);System.out.println("userAgent = " + userAgent);System.out.println("host = " + host);headers.forEach((k, v) -> System.out.println(k + " = " + v));System.out.println("令牌 = " + authorization);return u;}

属性说明

  • value/name: 要获取的请求头名称
  • required: 是否必须存在该请求头(默认为true)
  • defaultValue: 当请求头不存在时的默认值

6. @CookieValue - 获取Cookie的值

作用:@CookieValue 注解用于将控制器方法参数绑定到HTTP请求中的Cookie值。

使用场景
当需要获取客户端发送的特定Cookie时使用,例如:

  • 获取会话ID(JSESSIONID)
  • 获取用户偏好设置
  • 获取跟踪标识
示例一: 获取cookie中的属性值
    @RequestMapping(value = "/get-cookie", method =  RequestMethod.GET)public String getCookie(@CookieValue(name = "ry-cookie") String cookieValue,@CookieValue(name = "cookie") String cookie) {return cookieValue +" "+cookie;}

属性说明:

  • value/name: 要获取的Cookie名称
  • required: 是否必须存在该Cookie(默认为true)
  • defaultValue: 当Cookie不存在时的默认值
示例二:获取cookie对象
    /*** 获取指定名称的整个 Cookie 对象,并返回其详细信息* 通过 Cookie 对象可以访问 Cookie 的所有属性,如域名、路径、安全标志等* @param cookie 通过 @CookieValue 注解自动注入的名称为 "cookie" 的 Cookie 对象*               如果请求中没有此 Cookie,将抛出异常(可通过 required=false 避免)* @return 包含 Cookie 所有属性的格式化字符串,格式为:domain=值;path=值;name=值;secure=值;value=值;maxAge=值* 可用于调试或显示 Cookie 的完整信息*/@RequestMapping(value = "/get-cookie2", method = RequestMethod.GET)public String getCookie2(@CookieValue("cookie") Cookie cookie) {// 获取 Cookie 的域名属性(设置此 Cookie 的域名)String domain = cookie.getDomain();// 获取 Cookie 的值(实际存储的数据)String value = cookie.getValue();// 获取 Cookie 的路径属性(指定此 Cookie 在哪个路径下有效)String path = cookie.getPath();// 获取 Cookie 的名称(键)String name = cookie.getName();// 获取 Cookie 的安全标志(是否仅通过 HTTPS 传输)boolean secure = cookie.getSecure();// 获取 Cookie 的最大存活时间(秒),-1 表示浏览器关闭后过期,0 表示立即过期int maxAge = cookie.getMaxAge();// 将所有 Cookie 属性拼接成字符串返回return "domain=" + domain + ";path=" + path + ";name=" + name +";secure=" + secure + ";value=" + value + ";maxAge=" + maxAge;}

7. @RequestPart - 获取文件上传部分信息

作用@RequestPart 注解用于处理 multipart/form-data 请求,通常用于文件上传。它可以绑定到方法参数,处理上传的文件或表单字段。

  • 并且可以处理json数据绑定,直接将json解析到类对象中。前提条件是在前端JavaScript中封装为blob类型方能接收。

使用场景
主要用于文件上传场景,例如:

  • 用户头像上传
  • 文档上传
  • 多文件批量上传
示例一:多文件上传与form表单提交
  • 代码示例:
    /*** 文件上传:前提条件:Post请求,请求类型(Content-type)为multipart/form-data** @param user  form表单提交* @param file  单文件* @param files 多文件*/@RequestMapping(value = "/file-upload2", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)public String fileUpload2(@ModelAttribute User user,@RequestPart("file") MultipartFile file,@RequestPart("files") MultipartFile[] files) throws IOException {// user中接受三个字段
//        private Integer id;
//        private String name;
//        private String password;// 1. 文件名称String name = file.getOriginalFilename();// 2. 文件大小long size = file.getSize();// 3. 文件流InputStream inputStream = file.getInputStream();// 4. 保存文件file.transferTo(new File("D://IMAGE//" + Math.random() + name));// 5. 多文件个个数int length = files.length;for (MultipartFile multipartFile : files) {// 6. 统一的文件保存multipartFile.transferTo(new File("D://IMAGE//" + multipartFile.getOriginalFilename()));}return "参数信息为:" + user + ";单文件上传名称为:" + name + "; 多文件上传共上传了:" + length + "个文件";}
  • 前端代码示例:
    要求:form 表单提交用户ID、用户名、密码
<body>
<div class="container"><h2>文件上传示例</h2><form action="http://localhost:8080/mvc/user/file-upload2" method="post" enctype="multipart/form-data"><label>用户ID:</label><input type="text" name="id" placeholder="请输入用户ID" required/><label>用户名:</label><input type="text" name="name" placeholder="请输入用户名" required/><label>密码:</label><input type="password" name="password" placeholder="请输入密码" required/><label>单文件上传:</label><input type="file" name="file" required/><label>多文件上传:</label><input type="file" name="files" multiple="multiple" required/><div class="hint">按住 Ctrl 或 Cmd 键可选择多个文件</div><button type="submit">上传文件</button></form>
</div>
</body>
  • HTTP请求报文:

参考:理解 multipart/form-data 中的 boundary:文件上传的关键

POST http://localhost:8080/mvc/user/file-upload2 HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 668
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="140", "Not=A?Brand";v="24", "Microsoft Edge";v="140"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Origin: null
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9SoBhjF6rKJ4xNgh
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6------WebKitFormBoundary9SoBhjF6rKJ4xNgh
Content-Disposition: form-data; name="id"12
------WebKitFormBoundary9SoBhjF6rKJ4xNgh
Content-Disposition: form-data; name="name"12
------WebKitFormBoundary9SoBhjF6rKJ4xNgh
Content-Disposition: form-data; name="password"13
------WebKitFormBoundary9SoBhjF6rKJ4xNgh
Content-Disposition: form-data; name="file"; filename="文件1.txt"
Content-Type: text/plain文件内容:第一个文件
------WebKitFormBoundary9SoBhjF6rKJ4xNgh
Content-Disposition: form-data; name="files"; filename="文件2.txt"
Content-Type: text/plain文件内容:第二个文件
------WebKitFormBoundary9SoBhjF6rKJ4xNgh--
示例二:多文件上传与JSON传输

若想使用@RequestPart来处理json数据,则前端代码必须使用:Blob或File类型

  1. 如果我们想在后端使用 @RequestPart注解传输文件的同时来接收JSON数据,并且希望Spring MVC能够自动将JSON字符串转换为Java对象,那么前端必须将JSON数据作为Blob(或File)类型附加到FormData中,并设置正确的Content-Type(即application/json)
  • 代码示例:
    @RequestMapping(value = "/file-upload3", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)public String fileUpload3(@RequestPart("user") User user,@RequestPart("file") MultipartFile file,@RequestPart("files") MultipartFile[] files) throws IOException {// user中接受三个字段
//        private Integer id;
//        private String name;
//        private String password;System.out.println("user = " + user);// 1. 文件名称String name = file.getOriginalFilename();// 2. 文件大小long size = file.getSize();// 3. 文件流InputStream inputStream = file.getInputStream();// 4. 保存文件file.transferTo(new File("D://IMAGE//" + Math.random() + name));// 5. 多文件个个数int length = files.length;for (MultipartFile multipartFile : files) {// 6. 统一的文件保存multipartFile.transferTo(new File("D://IMAGE//" + multipartFile.getOriginalFilename()));}return "参数信息为:" + user + ";单文件上传名称为:" + name + "; 多文件上传共上传了:" + length + "个文件";}
  • 前端代码示例:
<script>document.addEventListener('DOMContentLoaded', function () {const form = document.getElementById('uploadForm');const previewBtn = document.getElementById('previewBtn');const jsonPreview = document.getElementById('jsonPreview');const statusMessage = document.getElementById('statusMessage');function showStatus(message, isSuccess) {statusMessage.textContent = message;statusMessage.className = isSuccess ? 'status-message success' : 'status-message error';statusMessage.style.display = 'block';setTimeout(() => {statusMessage.style.display = 'none';}, 5000);}previewBtn.addEventListener('click', function () {const user = {id: document.getElementById('id').value,name: document.getElementById('name').value,password: document.getElementById('password').value};jsonPreview.textContent = JSON.stringify(user, null, 2);jsonPreview.style.display = 'block';});form.addEventListener('submit', function (e) {e.preventDefault();// 创建FormData对象const formData = new FormData();const userData = {id: document.getElementById('id').value,name: document.getElementById('name').value,password: document.getElementById('password').value};formData.append('user', new Blob([JSON.stringify(userData)], {type: "application/json"}));const singleFile = document.querySelector('input[name="file"]').files[0];if (singleFile) {formData.append('file', singleFile);}const multiFiles = document.querySelector('input[name="files"]').files;for (let i = 0; i < multiFiles.length; i++) {formData.append('files', multiFiles[i]);}showStatus('正在上传文件,请稍候...', true);// 详细的fetch请求fetch(form.action, {method: 'POST',body: formData,// 添加mode选项mode: 'cors',// 添加credentials选项credentials: 'omit'}).then(response => {console.log('响应收到,状态码:', response.status);console.log('响应URL:', response.url);console.log('响应类型:', response.type);// 检查响应类型const contentType = response.headers.get('content-type');console.log('Content-Type:', contentType);if (contentType && contentType.includes('application/json')) {return response.json().then(data => {console.log('JSON响应数据:', data);return {success: true, data: data, type: 'json'};});} else {return response.text().then(text => {console.log('文本响应数据:', text);return {success: true, data: text, type: 'text'};});}}).then(result => {if (result.success) {if (result.type === 'json') {showStatus('上传成功!JSON响应: ' + JSON.stringify(result.data, null, 2), true);} else {showStatus('上传成功!响应: ' + result.data, true);}}}).catch(error => {console.error('完整错误信息:', error);console.error('错误名称:', error.name);console.error('错误消息:', error.message);// 更详细的错误分析if (error.name === 'TypeError') {console.error('可能是网络错误、CORS问题或响应解析错误');}showStatus('上传失败: ' + error.message, false);});});});
</script>
  • HTTP请求报文:
POST http://localhost:8080/mvc/user/file-upload3 HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 566
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryhMTAuUnQkQoA82lA
Accept: */*
Origin: null
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6------WebKitFormBoundaryhMTAuUnQkQoA82lA
Content-Disposition: form-data; name="user"; filename="blob"
Content-Type: application/json{"id":"11","name":"22","password":"33"}
------WebKitFormBoundaryhMTAuUnQkQoA82lA
Content-Disposition: form-data; name="file"; filename="文件1.txt"
Content-Type: text/plain文件内容:第一个文件
------WebKitFormBoundaryhMTAuUnQkQoA82lA
Content-Disposition: form-data; name="files"; filename="文件2.txt"
Content-Type: text/plain文件内容:第二个文件
------WebKitFormBoundaryhMTAuUnQkQoA82lA--

8. HttpEntity封装请求原始数据

HttpEntity 是 Spring Framework 中的一个核心类,用于表示 HTTP 请求或响应的完整内容,包括头信息(headers)主体内容(body)。它是 Spring 的 HTTP 消息抽象的核心组成部分。

核心特征:

  • 封装了 HTTP 消息的头信息和主体
  • 既可以用于请求,也可以用于响应
  • 提供了访问和操作 HTTP 消息的便捷方法
  • RequestEntityResponseEntity 的基类
如何使用 HttpEntity

1. 作为控制器方法参数(接收请求)
HttpEntity 用作控制器方法的参数时,Spring 会自动将传入的 HTTP 请求解析并注入:

@PostMapping("/process")
public ResponseEntity<String> processRequest(HttpEntity<String> httpEntity) {// 获取请求头HttpHeaders headers = httpEntity.getHeaders();String contentType = headers.getContentType().toString();String authToken = headers.getFirst("Authorization");// 获取请求体String requestBody = httpEntity.getBody();// 处理逻辑...System.out.println("收到请求,内容类型: " + contentType);System.out.println("认证令牌: " + authToken);System.out.println("请求体: " + requestBody);// 返回响应return ResponseEntity.ok("处理成功");
}

2. 作为返回值(返回自定义响应)

@GetMapping("/custom-response")
public HttpEntity<String> createCustomResponse() {// 创建自定义头HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);headers.set("X-Custom-Header", "custom-value");headers.setCacheControl("no-cache");// 创建响应体String responseBody = "{\"status\":\"success\", \"message\":\"Hello World\"}";// 返回 HttpEntityreturn new HttpEntity<>(responseBody, headers);
}
什么时候使用 HttpEntity?

1. 需要完全控制 HTTP 请求/响应时

  • 当你需要精确设置或读取 HTTP 头信息时并且一次性夺取多个:
  • 使用@RequestHeader读取多个不太方便时
// 需要设置特定的认证头
@PostMapping("/secure-data")
public ResponseEntity<String> getSecureData() {HttpHeaders headers = new HttpHeaders();headers.setBearerAuth("your-token-here");headers.set("X-API-Version", "2.0");HttpEntity<Void> request = new HttpEntity<>(headers);RestTemplate restTemplate = new RestTemplate();return restTemplate.exchange("https://api.secure.com/data",HttpMethod.GET,request,String.class);
}

2. 处理非标准 HTTP 消息时
当需要处理特殊的 Content-Type 或自定义头时:

@PostMapping(value = "/xml-data", consumes = "application/xml")
public ResponseEntity<String> processXmlData(HttpEntity<String> xmlEntity) {// 验证内容类型if (!MediaType.APPLICATION_XML.includes(xmlEntity.getHeaders().getContentType())) {return ResponseEntity.badRequest().body("期望 XML 内容");}// 处理 XML 数据String xmlData = xmlEntity.getBody();// XML 解析和处理逻辑...return ResponseEntity.ok("XML 处理成功");
}

3. 需要访问请求的完整信息时
当方法需要同时访问头信息和主体时:

@PostMapping("/log-request")
public ResponseEntity<Void> logIncomingRequest(HttpEntity<byte[]> requestEntity) {// 记录所有头信息requestEntity.getHeaders().forEach((name, values) -> {System.out.println(name + ": " + values);});// 记录主体大小byte[] body = requestEntity.getBody();System.out.println("请求体大小: " + (body != null ? body.length : 0) + " 字节");return ResponseEntity.ok().build();
}

4. 构建自定义响应时
当需要返回具有特定头的响应时:

@GetMapping("/download")
public HttpEntity<byte[]> downloadFile() {// 读取文件内容byte[] fileContent = readFileContent("report.pdf");// 设置下载头HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_PDF);headers.setContentDispositionFormData("attachment", "report.pdf");headers.setContentLength(fileContent.length);return new HttpEntity<>(fileContent, headers);
}
HttpEntity 的特殊化子类

Spring 提供了两个更专用的 HttpEntity 子类:

  1. RequestEntity:专门用于表示 HTTP 请求,包含 HTTP 方法信息:
// 创建 RequestEntity
RequestEntity<String> request = RequestEntity.post(URI.create("https://api.example.com/data")).header("Authorization", "Bearer token123").contentType(MediaType.APPLICATION_JSON).body("{\"key\":\"value\"}");// 发送请求
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(request, String.class);
  1. ResponseEntity:专门用于表示 HTTP 响应,包含状态码信息:
// 创建各种响应
@GetMapping("/data")
public ResponseEntity<String> getData() {try {String data = fetchDataFromDatabase();// 返回成功响应return ResponseEntity.ok().header("Cache-Control", "max-age=3600").body(data);} catch (DataNotFoundException e) {// 返回 404 响应return ResponseEntity.notFound().build();} catch (Exception e) {// 返回 500 响应return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("服务器错误: " + e.getMessage());}
}
实际应用示例
示例 1:处理文件上传并返回元数据
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Map<String, Object>> handleFileUpload(HttpEntity<MultipartHttpServletRequest> httpEntity) {MultipartHttpServletRequest request = httpEntity.getBody();HttpHeaders headers = httpEntity.getHeaders();// 获取上传的文件MultipartFile file = request.getFile("file");String originalFilename = file.getOriginalFilename();long fileSize = file.getSize();// 获取其他表单数据String description = request.getParameter("description");// 处理文件(保存到存储等)String fileId = saveFile(file);// 构建响应Map<String, Object> response = new HashMap<>();response.put("fileId", fileId);response.put("originalName", originalFilename);response.put("size", fileSize);response.put("description", description);response.put("uploadedAt", new Date());// 返回 JSON 响应HttpHeaders responseHeaders = new HttpHeaders();responseHeaders.setContentType(MediaType.APPLICATION_JSON);return new ResponseEntity<>(response, responseHeaders, HttpStatus.CREATED);
}
示例 2:代理请求到另一个服务
@PostMapping("/proxy")
public ResponseEntity<String> proxyRequest(HttpEntity<String> incomingRequest) {// 复制原始请求的头和体HttpHeaders outgoingHeaders = new HttpHeaders();outgoingHeaders.putAll(incomingRequest.getHeaders());// 添加或修改头outgoingHeaders.set("X-Forwarded-For", "proxy-server");// 创建转发请求HttpEntity<String> outgoingRequest = new HttpEntity<>(incomingRequest.getBody(), outgoingHeaders);// 转发到目标服务RestTemplate restTemplate = new RestTemplate();return restTemplate.exchange("https://target-service.com/api/endpoint",HttpMethod.POST,outgoingRequest,String.class);
}
示例3:文件下载示例
    /*** 文件下载* @param file 文件名称* @return ResponseEntity*/@RequestMapping("/download/{file}")public ResponseEntity<byte[]> download(@PathVariable String file) throws IOException {// 文件流InputStream inputStream = new FileInputStream("C:\\Users\\Administrator\\Pictures\\wifi\\ipv4.png");// 问题:1. 这种方式下载文件,中文名称无法识别// 问题:2. 文件太大,会oom内存溢出// 转为byte类型byte[] readAllBytes = inputStream.readAllBytes();return ResponseEntity.ok()// 内容类型:application/octet-stream.contentType(MediaType.APPLICATION_OCTET_STREAM)// 内容大小.contentLength(readAllBytes.length)// Content-Disposition 内容处理方式.header("Content-Disposition", "attachment; filename=" +file ).body(readAllBytes);}
示例4:文件下载标准格式示例(推荐)
    /*** 极简文件下载* @param file 文件名称* @return ResponseEntity*/@RequestMapping("/download2/{file}")public ResponseEntity<InputStreamResource> download2(@PathVariable String file) throws IOException {// 文件流InputStream inputStream = new FileInputStream("C:\\Users\\Administrator\\Pictures\\wifi\\ipv4.png");// 问题:1. 这种方式下载文件,中文名称无法识别,使用URLEncoder解决解决乱码问题,对中文进行url编码。String file_encode = URLEncoder.encode(file, StandardCharsets.UTF_8);// 问题:2. 文件太大下载时,会oom内存溢出,InputStreamResource resource = new InputStreamResource(inputStream);return ResponseEntity.ok()// 内容类型:application/octet-stream.contentType(MediaType.APPLICATION_OCTET_STREAM)// 内容大小.contentLength(inputStream.available())// Content-Disposition 内容处理方式.header("Content-Disposition", "attachment; filename=" +file_encode ).body(resource);}

最佳实践

  1. 优先使用 ResponseEntity 和 RequestEntity:它们提供了更丰富的API和更好的类型安全性。

  2. 适当处理空体:对于没有主体的请求或响应,可以使用 HttpEntity<Void>

  3. 注意内存使用:对于大文件或大量数据,考虑使用流式处理而不是一次性加载到内存。

  4. 合理使用头信息:遵循 HTTP 标准,正确设置 Content-Type、Cache-Control 等头信息。

  5. 异常处理:始终处理可能出现的异常,并提供适当的错误响应。

总结

HttpEntity 是 Spring 中处理 HTTP 消息的核心抽象,它提供了对 HTTP 请求和响应的完整控制。通过 HttpEntity 及其子类(RequestEntity 和 ResponseEntity),开发者可以:

  • 精确控制 HTTP 头信息
  • 处理各种内容类型的数据
  • 构建符合标准的 HTTP 响应
  • 实现高级功能如请求代理、文件处理等

在需要完全控制 HTTP 消息的各个方面时,HttpEntity 是一个强大而灵活的工具。

简单代码

    @RequestMapping(value = "/http-entity")public String httpEntity(HttpEntity<String> httpEntity) {// 获取http请求报文头HttpHeaders headers = httpEntity.getHeaders();System.out.println("headers = " + headers);// 获取context-type 类型MediaType contentType = headers.getContentType();System.out.println("contentType = = " + contentType);// 获取请求体String body = httpEntity.getBody();System.out.println("body = " + body);return headers + body;}

HTTP请求报文

POST http://localhost:8080/mvc/user/http-entity HTTP/1.1
User-Agent: PostmanRuntime/7.46.1
Accept: */*
Postman-Token: 8b8203b7-af8f-473e-8e28-0a7ef53ca36c
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 25username=111&password=222

结果输出:

headers = [user-agent:"PostmanRuntime/7.46.1", accept:"*/*", postman-token:"8b8203b7-af8f-473e-8e28-0a7ef53ca36c", host:"localhost:8080", accept-encoding:"gzip, deflate, br", connection:"keep-alive", content-length:"25", Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"]
contentType = application/x-www-form-urlencoded;charset=UTF-8
body = username=111&password=222

9. 原生API

代码示例:

    @RequestMapping(value = "servlet")public void servlet(HttpServletRequest request, HttpServletResponse response) throws IOException {String username = request.getParameter("username");String password = request.getParameter("password");// 自定义请求头response.setHeader("TEST-SERVLET", username+":"+password);response.getWriter().write("ok"+username+":"+password);}

HTTP请求报文

POST http://localhost:8080/mvc/user/servlet?username=111&password=200 HTTP/1.1
User-Agent: PostmanRuntime/7.46.1
Accept: */*
Postman-Token: feb64c8b-b9e0-4305-b159-8709d859b6f5
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 25

HTTP响应报文

  • 我们自定义的header头返回了
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: *
TEST-SERVLET: 111:200
Content-Length: 0
Date: Thu, 18 Sep 2025 09:58:43 GMT
Keep-Alive: timeout=60
Connection: keep-alive

总结与最佳实践

场景推荐注解说明
定义请求入口@GetMapping, @PostMapping优先使用组合注解,意图更清晰。
获取单个URL参数@RequestParam用于查询字符串和表单字段。
获取RESTful路径变量@PathVariable用于从 URL 路径中获取值。
获取JSON请求体@RequestBody构建 RESTful API 时常用。
获取表单对象@ModelAttribute处理传统表单提交,可自动绑定到对象。
获取请求头/Cookie@RequestHeader, @CookieValue用于需要这些信息的特定场景。

@RequestParam、@RequestBody以及@ModelAttribute的区别和异同?

@RequestParam@RequestBody@ModelAttribute 都用于从HTTP请求中提取数据,但它们的工作方式、用途和场景有根本性的区别。

下面我将通过一个对比表格和详细解释来阐明三者的区别和异同。

核心区别对比表
特性@RequestParam@ModelAttribute@RequestBody
核心用途获取单个请求参数多个参数绑定到对象请求体绑定到对象
数据来源URL 查询字符串表单数据URL查询字符串表单数据路径变量请求体 (Body)
HTTP方法主要用于 GET,也可用于 POST主要用于 GET/POST (表单)主要用于 POST, PUT, PATCH
数据格式key1=value1&key2=value2key1=value1&key2=value2JSONXML 等 (由Content-Type指定)
Content-Typeapplication/x-www-form-urlencodedapplication/x-www-form-urlencodedapplication/json, application/xml
处理过程简单类型转换数据绑定 (Data Binding)HttpMessageConverter 进行反序列化
返回值一个简单类型的值一个复杂对象一个复杂对象
典型场景分页(page)、搜索(keyword)、ID(id)表单提交(用户注册、编辑)RESTful API,接收前端发送的JSON数据

详细解释与示例

为了更好地理解,我们假设有一个 User 类:

public class User {private String username;private String password;private Integer age;
}
1. @RequestParam:获取单个参数
  • 作用:用于从请求URL的查询字符串(Query String)表单数据(Form Data) 中提取单个值。
  • 来源:URL中 ? 后面的部分,例如 ?name=John&age=30
  • 处理:Spring进行简单的类型转换(如 String -> Integer)。

示例场景:通过URL参数获取用户信息。

// 请求:GET /user?name=John&age=30@GetMapping("/user")
public String getUser(@RequestParam String name, @RequestParam Integer age) {// 使用 name 和 agereturn "user-page";
}
2. @ModelAttribute:绑定到对象(表单处理)
  • 作用:将请求中的多个参数(通常是表单字段)智能地绑定到一个复合对象上。
  • 来源:主要来自表单提交的字段。Spring会自动将参数名与对象属性名匹配。
  • 处理:数据绑定(Data Binding) + 可选的验证(Validation)。

示例场景:传统的HTML表单提交。

// 请求:POST /register (Body: username=John&password=123&age=30)@PostMapping("/register")
public String register(@ModelAttribute User user) { // Spring 自动创建User对象并调用setter方法:// user.setUsername("John");// user.setPassword("123");// user.setAge(30);userService.save(user);return "success";
}
3. @RequestBody:解析请求体(API开发)
  • 作用:将整个请求体(Request Body) 的内容(通常是JSON)解析并转换(反序列化)为一个Java对象。
  • 来源:请求体(Body),不是URL参数
  • 处理:使用配置的 HttpMessageConverter(如 MappingJackson2HttpMessageConverter)来解析JSON等格式。

示例场景:开发RESTful API,接收前端发送的JSON数据。

// 请求:POST /api/users
// Headers: Content-Type: application/json
// Body: {"username": "John", "password": "123", "age": 30}@PostMapping("/api/users")
public ResponseEntity<User> createUser(@RequestBody User user) {// Spring 使用Jackson库将JSON字符串反序列化成User对象User savedUser = userService.save(user);return ResponseEntity.ok(savedUser);
}
关键异同总结
  1. @RequestParam vs @ModelAttribute

    • 相同点:都主要用于处理 application/x-www-form-urlencoded 格式的数据(即URL查询参数或表单提交)。
    • 不同点@RequestParam 是“零售”,一个一个地拿参数;@ModelAttribute 是“批发”,一次性把一个表单的所有字段组装成一个对象。在简单场景下,@ModelAttribute 可以省略不写,Spring会默认应用此规则。
  2. @ModelAttribute vs @RequestBody

    • 根本区别:它们处理的数据格式和Content-Type完全不同。
      • @ModelAttribute:期望 Content-Type: application/x-www-form-urlencoded。数据是扁平化的键值对。
      • @RequestBody:期望 Content-Type: application/json。数据是结构化的JSON(或XML)。
    • 这是导致你之前遇到 415 Unsupported Media Type 错误的核心原因。如果你用 @RequestBody 去接收一个表单提交,或者用 @ModelAttribute 去接收一个JSON请求体,都会报错。
  3. @RequestParam vs @RequestBody

    • 它们处在完全不同的“维度”。@RequestParam 处理的是URL的一部分,而 @RequestBody 处理的是HTTP请求的消息体。
如何选择?
  • 开发传统的网页应用(多页面应用,MPA),使用表单提交:主要使用 @ModelAttribute(或省略)。
  • 开发现代的前后端分离应用(如React/Vue + Spring Boot API):主要使用 @RequestBody 来接收JSON数据。
  • 在任何场景下,只需要获取一两个简单的查询参数(如ID、搜索词、分页参数):使用 @RequestParam

简单记忆:表单用@ModelAttribute,JSON用@RequestBody,小参数用@RequestParam

@RequestHeader, @CookieValue, @RequestPart对比总结

注解作用数据来源常用场景
@RequestHeader获取HTTP请求头请求头部获取客户端信息、认证令牌等
@CookieValue获取Cookie值请求Cookie获取会话ID、用户偏好等
@RequestPart获取multipart请求部分请求体(multipart)文件上传、复杂表单提交
http://www.dtcms.com/a/391591.html

相关文章:

  • 【DMA】DMA入门:外设数据到内存,以串口DMA接收为例,解析底层实现
  • Java 中 super 和 this关键字总结
  • 我的创作纪念日 ----- 第512天
  • 【docker】删除镜像
  • 亚马逊 MWS 关键字 API 实战:关键字搜索商品列表接口深度解析与优化方案
  • 博文干货 | Pulsar 平均负载器(AvgShedder)
  • 【硬件】嘉立创专业版layout流程(一)
  • PyQt6之分组框
  • 深度剖析 IM 单聊与群聊架构设计
  • 农业自动化:技术重塑传统农业的新范式
  • Nginx 日志文件在哪?
  • 小程序开发者转多端应用app调整视频播放功能
  • 九、Java-注解
  • Java学习笔记——AI插件、新建模块、算数运算符类型、隐式转换、强制转换、自增自减运算符、赋值运算符、关系运算符、逻辑运算符、三元运算符
  • 【从零开始刷力扣006】leetcode206
  • FreeRTOS——介绍及移植过程
  • Day 07 Physics list-----以B1为例
  • 重读一次IS015765-2,记录对错误和异常处理的方式
  • Edge浏览器CSDN文章编辑时一按shift就乱了(Edge shift键)欧路翻译问题(按Shift翻译鼠标所在段落)
  • SpringIoc 基础练习 验证码
  • 前端项目,CDN预热有什么用?
  • TF卡的存储数据结构—fat32格式
  • led的带宽在模拟太阳光中设备中的影响
  • go资深之路笔记(三) sync.WaitGroup, sync.errgroup和 sync.go-multierror
  • Docker 与数据库环境
  • Node.js 模块系统详解
  • proxy代理应用记录
  • 基于python大数据的汽车数据分析系统设计与实现
  • WebSocket实现原理
  • 从保存到加载Docker镜像文件操作全图解