Spring MVC 注解驱动控制器深度解析

以下是为您精心撰写的《Spring MVC 注解驱动控制器深度解析》完整文档,系统性地分类讲解所有核心注解,深入剖析参数绑定原理,并提供符合 RESTful API 最佳实践的工业级实战示例,每个示例均附带详尽中文注释,助您从“会用”进阶到“精通”。


📜 Spring MVC 注解驱动控制器深度解析

目标:全面掌握 Spring MVC 所有核心注解,理解其作用、协作关系与底层工作原理,构建符合 RESTful 标准的高性能 API


✅ 一、Spring MVC 注解分类总览

Spring MVC 的注解体系庞大,但可清晰划分为以下六大类:

类别注解作用适用场景
1. 控制器声明@Controller, @RestController声明一个类为 Spring MVC 控制器区分视图渲染与 REST API
2. 请求映射@RequestMapping, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping将 HTTP 请求映射到具体方法定义 API 路径与方法
3. 请求参数绑定@RequestParam, @PathVariable, @RequestBody, @RequestHeader, @CookieValue, @RequestPart绑定 HTTP 请求中的各种数据到方法参数获取 Query、Path、Body、Header 等
4. 模型与视图@ModelAttribute, @SessionAttribute, @SessionAttributes操作模型数据和会话表单预填充、会话数据共享
5. 响应控制@ResponseBody, @ResponseStatus控制响应体内容与 HTTP 状态码返回 JSON/XML、自定义状态码
6. 参数校验@Valid, @Validated, @NotNull, @Size, @Email声明式数据校验保证 API 输入合法性

核心原则
REST API 开发中,推荐使用 @RestController + @GetMapping/@PostMapping + @RequestBody + @PathVariable + @Valid + @ResponseStatus 的组合。


✅ 二、核心注解详解与工业级实战示例

✅ 2.1 控制器声明:@Controller@RestController

注解作用区别
@Controller声明一个 Spring MVC 控制器,默认返回视图名需配合 @ResponseBody 才能返回 JSON
@RestController@Controller + @ResponseBody 的组合注解所有方法默认返回响应体(JSON/XML),REST API 首选
🎯 实战示例:RESTful 用户管理 API(推荐写法)
package com.example.controller;

import com.example.dto.UserCreateRequest;
import com.example.dto.UserResponse;
import com.example.service.UserService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * ✅ 工业级 RESTful API 控制器
 * 使用 @RestController,所有方法默认返回 JSON,无需额外 @ResponseBody
 * 路径设计遵循 RESTful 规范:复数名词、HTTP 方法语义化
 */
@RestController
@RequestMapping("/api/users") // ✅ 1. 控制器级别路径前缀
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * ✅ 获取所有用户(GET /api/users)
     * 符合 RESTful:集合资源使用复数名词
     * 返回:List<UserResponse> → 自动序列化为 JSON
     */
    @GetMapping
    public ResponseEntity<List<UserResponse>> getAllUsers() {
        List<UserResponse> users = userService.findAll();
        return ResponseEntity.ok(users); // ✅ 200 OK + JSON
    }

    /**
     * ✅ 根据 ID 获取单个用户(GET /api/users/{id})
     * 路径变量 {id} 用于标识唯一资源
     * 返回:UserResponse → 自动序列化为 JSON
     */
    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) {
        UserResponse user = userService.findById(id);
        return ResponseEntity.ok(user); // ✅ 200 OK
    }

    /**
     * ✅ 创建新用户(POST /api/users)
     * 创建资源使用 POST
     * 请求体为 JSON,使用 @RequestBody 绑定
     * 返回:201 Created + Location 头(最佳实践)
     */
    @PostMapping
    public ResponseEntity<UserResponse> createUser(@Valid @RequestBody UserCreateRequest request) {
        // ✅ @Valid 触发 JSR-380 校验,失败会抛出异常,由全局异常处理器处理
        UserResponse createdUser = userService.create(request);
        // ✅ 201 Created 是创建资源的标准状态码
        // ✅ Location 头指向新创建资源的 URI,符合 RESTful 规范
        return ResponseEntity.status(HttpStatus.CREATED)
                .header("Location", "/api/users/" + createdUser.getId())
                .body(createdUser);
    }

    /**
     * ✅ 更新用户信息(PUT /api/users/{id})
     * PUT:更新整个资源(全量更新)
     * 使用 @PathVariable 获取资源 ID
     * 使用 @RequestBody 接收更新数据
     */
    @PutMapping("/{id}")
    public ResponseEntity<UserResponse> updateUser(@PathVariable Long id,
                                                   @Valid @RequestBody UserCreateRequest request) {
        UserResponse updatedUser = userService.update(id, request);
        return ResponseEntity.ok(updatedUser); // ✅ 200 OK
    }

    /**
     * ✅ 部分更新用户信息(PATCH /api/users/{id})
     * PATCH:仅更新部分字段(推荐用于增量更新)
     * 本例中,我们只允许更新邮箱
     */
    @PatchMapping("/{id}")
    public ResponseEntity<UserResponse> patchUser(@PathVariable Long id,
                                                  @RequestBody UserPartialUpdateRequest request) {
        UserResponse patchedUser = userService.patch(id, request);
        return ResponseEntity.ok(patchedUser); // ✅ 200 OK
    }

    /**
     * ✅ 删除用户(DELETE /api/users/{id})
     * 删除资源使用 DELETE
     * 通常返回 204 No Content,表示成功但无响应体
     */
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build(); // ✅ 204 No Content(推荐)
    }
}

RESTful 规范总结

  • 资源命名:使用复数名词(/users),非动词(/getUser
  • HTTP 方法
    • GET:读取资源
    • POST:创建资源
    • PUT:全量更新资源
    • PATCH:部分更新资源
    • DELETE:删除资源
  • 状态码
    • 200 OK:成功读取/更新
    • 201 Created:成功创建(必须带 Location
    • 204 No Content:成功删除
    • 400 Bad Request:参数校验失败
    • 404 Not Found:资源不存在
    • 500 Internal Server Error:系统内部错误
  • 响应体:统一返回 JSON,结构清晰(如 {code: 200, msg: "ok", data: {...}}

✅ 2.2 请求映射:@RequestMapping 及其快捷方式

注解等价于说明
@RequestMapping@RequestMapping(method = GET)通用,可指定 method、params、headers、consumes、produces
@GetMapping@RequestMapping(method = GET)推荐用于读取
@PostMapping@RequestMapping(method = POST)推荐用于创建
@PutMapping@RequestMapping(method = PUT)推荐用于全量更新
@DeleteMapping@RequestMapping(method = DELETE)推荐用于删除
@PatchMapping@RequestMapping(method = PATCH)推荐用于部分更新
🎯 实战示例:复杂映射配置
@RestController
@RequestMapping(value = "/api/products", // ✅ 1. 基础路径
                produces = MediaType.APPLICATION_JSON_VALUE, // ✅ 2. 默认返回 JSON
                consumes = MediaType.APPLICATION_JSON_VALUE) // ✅ 3. 默认接收 JSON
public class ProductController {

    /**
     * ✅ GET /api/products?category=electronics&minPrice=100
     * 使用 params 精确匹配查询参数
     * 使用 produces 明确声明返回类型
     */
    @GetMapping(params = {"category", "minPrice"})
    public ResponseEntity<List<Product>> getProductsByCategoryAndMinPrice(
            @RequestParam String category,
            @RequestParam Double minPrice) {
        // ...
    }

    /**
     * ✅ GET /api/products/{id}/images
     * 嵌套路径:资源的子资源
     */
    @GetMapping("/{id}/images")
    public ResponseEntity<List<String>> getProductImages(@PathVariable Long id) {
        // ...
    }

    /**
     * ✅ POST /api/products
     * 消费者(consumes)明确要求 JSON
     * 生产者(produces)明确返回 JSON
     */
    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE,
                 produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Product> createProduct(@RequestBody Product product) {
        // ...
    }

    /**
     * ✅ DELETE /api/products/{id}
     * 使用 headers 限制只能由管理员调用
     */
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT) // ✅ 4. 直接声明状态码,无需 ResponseEntity
    public void deleteProduct(@PathVariable Long id,
                              @RequestHeader("X-Admin-Token") String token) {
        if (!"admin123".equals(token)) {
            throw new SecurityException("权限不足");
        }
        productService.delete(id);
    }
}

建议
优先使用 @GetMapping 等快捷注解,代码更清晰。
@RequestMapping 用于控制器级别统一配置(如 producesconsumes),避免重复。


✅ 2.3 请求参数绑定:六大核心注解及其工作原理

🧠 核心原理:Spring MVC 参数绑定流程

当一个 HTTP 请求到达时,HandlerAdapter(通常是 RequestMappingHandlerAdapter)会执行以下步骤:

  1. 解析方法签名:获取所有参数及其注解(如 @PathVariable("id") Long id
  2. 参数解析器(ArgumentResolver):Spring 有数十个 HandlerMethodArgumentResolver 实现,按优先级匹配:
    • PathVariableMethodArgumentResolver → 解析 {id}
    • RequestParamMethodArgumentResolver → 解析 ?name=张三
    • RequestResponseBodyMethodProcessor → 解析 application/json Body
    • RequestHeaderMethodArgumentResolver → 解析 Authorization: Bearer xxx
    • CookieValueMethodArgumentResolver → 解析 Cookie: JSESSIONID=xxx
    • ModelMethodProcessor → 注入 Model model
    • ServletWebRequestMethodArgumentResolver → 注入 HttpServletRequest
  3. 类型转换:将字符串(如 "123")转为 LongLocalDate、枚举等
  4. 数据校验:若参数有 @Valid,调用 Validator(Hibernate Validator)
  5. 注入参数:将解析并转换后的值,注入到方法参数中
✅ 2.3.1 @RequestParam —— 绑定查询参数(Query String)或表单字段
@GetMapping("/search")
public ResponseEntity<List<Product>> searchProducts(
        @RequestParam String keyword, // ✅ 必须存在,否则 400
        @RequestParam(required = false) String category, // ✅ 可选,为 null 无报错
        @RequestParam(defaultValue = "10") int pageSize, // ✅ 默认值,缺失时使用 10
        @RequestParam(name = "page", defaultValue = "1") int pageNumber) { // ✅ name 映射参数名

    List<Product> products = productService.search(keyword, category, pageNumber, pageSize);
    return ResponseEntity.ok(products);
}

请求示例
GET /api/products/search?keyword=手机&category=数码&page=2&pageSize=20

工作原理
Spring 从 HttpServletRequest.getQueryString() 解析 key=value,根据参数名或 name 属性匹配。

✅ 2.3.2 @PathVariable —— 绑定 URL 路径变量
@GetMapping("/products/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
    Product product = productService.findById(id);
    return ResponseEntity.ok(product);
}

// ✅ 路径变量可与正则匹配
@GetMapping("/products/{id:\\d+}") // ✅ 仅匹配数字
public ResponseEntity<Product> getProductByNumericId(@PathVariable Long id) {
    // ...
}

// ✅ 多个路径变量
@GetMapping("/categories/{categoryId}/products/{productId}")
public ResponseEntity<Product> getProductInCategory(
        @PathVariable Long categoryId,
        @PathVariable Long productId) {
    // ...
}

请求示例
GET /api/products/1001

工作原理
Spring 使用 UriTemplate 解析 URL 模板(如 /products/{id}),提取 {id} 对应的值(1001),并转换为 Long

✅ 2.3.3 @RequestBody —— 绑定请求体(JSON/XML)
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest request) {
    // ✅ @Valid 触发校验
    User user = userService.create(request);
    return ResponseEntity.status(HttpStatus.CREATED).body(user);
}

请求体(JSON)

{
  "username": "zhangsan",
  "email": "zhangsan@example.com",
  "age": 25
}

工作原理

  1. HandlerAdapter 检查请求头 Content-Type: application/json
  2. 使用 HttpMessageConverter(默认 MappingJackson2HttpMessageConverter)将 JSON 字符串反序列化为 Java 对象(UserCreateRequest
  3. 反序列化过程使用 Jackson 的 ObjectMapper,通过字段名匹配(usernameusername
  4. 若对象有 @Valid,则调用 Validator.validate() 进行校验

⚠️ 重要

  • 必须有 Content-Type: application/json
  • JSON 字段名必须与 Java Bean 的属性名一致(区分大小写)
  • 若字段为 null,反序列化后仍为 null,需配合 @NotNull 校验
✅ 2.3.4 @RequestHeader —— 绑定请求头
@GetMapping("/profile")
public ResponseEntity<User> getProfile(
        @RequestHeader("Authorization") String token,
        @RequestHeader(value = "User-Agent", required = false) String userAgent) {

    // ✅ 从 token 解析用户信息
    User user = authService.getUserByToken(token);
    log.info("请求设备:{}", userAgent); // 可选

    return ResponseEntity.ok(user);
}

请求头

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36

工作原理
Spring 调用 HttpServletRequest.getHeader(String name) 获取对应头信息。

✅ 2.3.5 @CookieValue —— 绑定 Cookie
@GetMapping("/cart")
public ResponseEntity<Cart> getCart(@CookieValue(value = "JSESSIONID", required = false) String sessionId) {
    if (sessionId == null) {
        // 无会话,创建新购物车
        return ResponseEntity.ok(cartService.createEmptyCart());
    } else {
        // 根据 Session ID 获取购物车
        return ResponseEntity.ok(cartService.getCartBySession(sessionId));
    }
}

工作原理
Spring 调用 HttpServletRequest.getCookie(String name),获取 Cookie 数组,匹配名称。

✅ 2.3.6 @RequestPart —— 绑定 multipart 请求(文件上传)
@PostMapping("/upload")
public ResponseEntity<String> uploadAvatar(
        @RequestPart("avatar") MultipartFile avatar, // ✅ 文件
        @RequestPart("metadata") UserAvatarMetadata metadata) { // ✅ JSON 元数据

    // ✅ 上传文件
    String fileName = fileService.save(avatar);
    // ✅ 保存元数据
    metadataService.save(metadata);

    return ResponseEntity.ok("上传成功");
}

请求格式(multipart/form-data)

--boundary
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg

[二进制文件数据]

--boundary
Content-Disposition: form-data; name="metadata"
Content-Type: application/json

{"userId": 1001, "type": "profile"}
--boundary--

工作原理
Spring 使用 StandardMultipartHttpServletRequest 解析 multipart 请求,根据 name 属性匹配 MultipartFile@RequestBody 对象。


✅ 2.4 模型与视图:@ModelAttribute@SessionAttribute

⚠️ REST API 中极少使用,主要用于传统 Web(JSP/Thymeleaf)开发。

@ModelAttribute:预填充表单数据
// 在 Controller 方法前执行,用于填充 Model
@ModelAttribute
public void addAttributes(Model model) {
    model.addAttribute("categories", categoryService.findAll());
}

// 方法参数中使用,绑定表单对象
@PostMapping("/products")
public String createProduct(@Valid @ModelAttribute Product product, BindingResult result) {
    // ...
    return "product/list";
}
@SessionAttribute:从 Session 获取数据
@GetMapping("/cart")
public String showCart(@SessionAttribute("cart") Cart cart) {
    // 从 HttpSession 获取名为 "cart" 的属性
    return "cart/show";
}

REST API 建议
避免使用 Session,改用 JWT Token 传递用户信息,实现无状态。


✅ 2.5 响应控制:@ResponseBody@ResponseStatus

@ResponseBody:标记方法返回值为响应体

注意@RestController 已包含此注解,无需重复添加

@ResponseStatus:设置 HTTP 状态码
@GetMapping("/users/{id}")
@ResponseStatus(HttpStatus.OK) // ✅ 明确声明状态码
public User getUser(@PathVariable Long id) {
    return userService.findById(id);
}

@DeleteMapping("/users/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT) // ✅ 删除成功,无响应体
public void deleteUser(@PathVariable Long id) {
    userService.delete(id);
}

@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED) // ✅ 创建成功
public User createUser(@Valid @RequestBody UserCreateRequest request) {
    return userService.create(request);
}

推荐
在返回 ResponseEntity<T> 时,使用 ResponseEntity.status(HttpStatus.CREATED).body(...) 更灵活
在返回 T 时,使用 @ResponseStatus 更简洁。


✅ 2.6 参数校验:@Valid + JSR-380 注解

✅ 实战示例:DTO 校验
// DTO 类
@Data
public class UserCreateRequest {

    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20, message = "用户名长度必须在 2~20 之间")
    private String username;

    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, message = "密码至少 6 位")
    private String password;

    @Min(value = 18, message = "年龄不能小于 18 岁")
    @Max(value = 150, message = "年龄不能大于 150 岁")
    private Integer age;

    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;

    @Valid // ✅ 嵌套校验:UserAddress 是一个对象
    private UserAddress address;
}

@Data
public class UserAddress {
    @NotBlank
    private String province;

    @NotBlank
    private String city;

    @NotBlank
    private String detail;
}
✅ Controller 中触发校验
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest request) {
    // ✅ 如果校验失败,Spring 会抛出 MethodArgumentNotValidException
    // 由 @RestControllerAdvice 统一捕获并返回 400 错误
    User user = userService.create(request);
    return ResponseEntity.status(HttpStatus.CREATED).body(user);
}

工作原理

  1. RequestResponseBodyMethodProcessor 在反序列化后,检查参数是否有 @Valid
  2. 若有,则调用 Validator(Hibernate Validator)对对象进行校验
  3. 校验失败 → 抛出 MethodArgumentNotValidException
  4. @RestControllerAdvice 捕获该异常 → 返回统一格式的错误响应(如 {code:400, msg:"校验失败", errors:{...}}

嵌套校验@Valid 可用于对象属性,实现递归校验


✅ 三、参数注解工作原理深度总结

注解数据来源解析器类型转换校验支持
@RequestParamQuery String (?key=value)RequestParamMethodArgumentResolver✅(String → Integer/Date)✅(@Valid
@PathVariableURL 路径(/users/{id}PathVariableMethodArgumentResolver✅(String → Long/UUID)✅(@Valid
@RequestBodyHTTP Body(JSON/XML)RequestResponseBodyMethodProcessor✅(JSON → Java Object)✅(@Valid
@RequestHeaderHTTP 请求头RequestHeaderMethodArgumentResolver
@CookieValueCookieCookieValueMethodArgumentResolver
@RequestPartmultipart/form-dataRequestPartMethodArgumentResolver✅(JSON → Object)✅(@Valid

共同特点

  • 所有参数解析器都实现 HandlerMethodArgumentResolver 接口
  • 类型转换由 ConversionService(默认 DefaultFormattingConversionService)完成
  • 校验由 Validator(默认 LocalValidatorFactoryBean)完成
  • 所有过程发生在 HandlerAdapter.invokeHandlerMethod()

✅ 四、最佳实践与避坑指南

类别推荐做法避免做法
控制器使用 @RestController使用 @Controller + @ResponseBody
路径/api/users(复数),/api/users/{id}/getUserById
HTTP 方法GET(读)、POST(创)、PUT(全改)、PATCH(部分改)、DELETE(删)GET /deleteUser
状态码200 OK201 Created204 No Content400 Bad Request404 Not Found200 返回错误
参数绑定@RequestBody + @Valid 绑定 JSONrequest.getParameter("name")
文件上传@RequestPart("file") MultipartFile@RequestParam("file") String base64
校验DTO 上加 @Valid,Controller 方法参数上加 @Valid在 Controller 方法内手动校验
异常处理使用 @RestControllerAdvice 统一返回 {code, msg, data}每个方法写 try-catch
日志记录请求 ID、路径、耗时、状态码记录敏感信息(密码、Token)
安全使用 JWT、HTTPS、CORS 配置在 URL 传 Token

✅ 五、学习建议

阶段建议
1. 理解画出请求处理流程图:DispatcherServlet → HandlerMapping → HandlerAdapter → Controller → ViewResolver → View
2. 实践用 Spring Boot 创建一个完整 REST API:用户管理(CRUD + 分页 + 校验 + 异常处理)
3. 调试在 IDEA 中 Debug RequestMappingHandlerAdapter.invokeHandlerMethod(),看参数绑定如何发生
4. 阅读源码查看 RequestParamMethodArgumentResolver.javaRequestResponseBodyMethodProcessor.java
5. 工具使用 Postman 测试所有边界情况(空值、非法类型、超长字符串)
6. 标准学习 OpenAPI 3.0(Swagger),自动生成 API 文档

✅ 六、总结:Spring MVC 注解的本质

Spring MVC 注解是“声明式编程”的典范
你只需告诉框架:“我要这个参数”、“这个方法处理这个路径”、“这个对象要校验”
而不需要写任何解析、转换、校验的样板代码。

真正的架构师,不是会写 @GetMapping
而是理解它背后有 3 个解析器、1 个转换器、1 个校验器在默默工作

你已掌握 Spring MVC 的核心灵魂。
从现在起,你写出的每一个 API,都符合企业级标准。


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值