Spring MVC 常用注解详解

一、Spring MVC 注解体系总体认知

Spring MVC 是 Java Web 开发最核心的 MVC 框架之一,其强大之处除了 DispatcherServlet、HandlerMapping、HandlerAdapter 这些基础设施外,更关键的是一套高度抽象的 注解体系。这些注解帮助开发者以最少的样板代码实现请求映射、参数解析、响应输出、异常处理、数据校验及拦截等核心能力。

Spring MVC 注解大致可以分为以下几类:

  • 控制器相关注解

    • @Controller
    • @RestController
    • @RequestMapping / @GetMapping / @PostMapping
  • 请求参数与响应映射注解

    • @RequestParam
    • @PathVariable
    • @RequestBody
    • @ResponseBody
    • @CookieValue
    • @RequestHeader
  • 数据校验

    • @Valid / @Validated
    • 配合 Bean Validation 注解使用
  • JSON/XML 序列化控制

    • @JsonIgnore / @JsonProperty(来自 Jackson)
  • 作用于控制层流程的注解

    • @ModelAttribute
    • @SessionAttributes
    • @InitBinder
  • 全局异常处理

    • @ExceptionHandler
    • @ControllerAdvice

这一类注解合起来,构成了 Spring MVC 在 Web 层的全部能力。

为了让小白能更清晰地理解,我们按照实际开发流程来逐类讲解。


二、控制器注解

1. @Controller@RestController

原理

  • @Controller 基于 Spring 组件扫描,被注册为一个 Web 控制器组件。
  • @RestController = @Controller + @ResponseBody,表示控制器所有方法的返回值都以 JSON/XML 方式响应,而不是返回页面。

用途

  • @Controller 适用于 MVC + Thymeleaf/JSP 页面渲染
  • @RestController 用于 API 服务(前后端分离最常用)

思维图示

@Controller
    ↓
  返回视图(View)

@RestController
    ↓
  返回 JSON (自动序列化)

示例代码

@Controller
public class PageController {

    @GetMapping("/home")
    public String home() {
        return "home"; // 跳转到 home.html 页面
    }
}

@RestController
@RequestMapping("/api/user")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return new User(id, "Tom");
    }
}

实战场景

  • 后端返回 HTML 页面的老式 MVC 项目使用 @Controller
  • 前后端分离项目使用 @RestController

常见错误

  • @Controller 忘记加 @ResponseBody 导致返回字符串被当成视图名
  • 同时使用 @RestController 又返回 ModelAndView 会出现混乱

作者经验总结

大部分企业项目使用前后端分离,建议所有 API 控制层统一使用 @RestController,避免因忘记 @ResponseBody 造成的问题。


三、请求映射注解体系

Spring MVC 提供一套族式注解用于请求路由:

  • @RequestMapping
  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

1. @RequestMapping

原理

@RequestMapping 核心是 指定 URL、请求方法、参数、绑定条件。底层由 RequestMappingHandlerMapping 进行处理。

示例

@RestController
@RequestMapping("/api/user")
public class UserController {

    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public List<String> list() {
        return List.of("A", "B");
    }
}

2. @GetMapping 等快捷注解

这些注解是 @RequestMapping(method=...) 的语法糖。

示例:

@GetMapping("/find/{id}")
public User find(@PathVariable Long id) {
    return new User(id, "Tom");
}

URL 组合逻辑图(伪图示)

类级别 @RequestMapping("/api/user")
            +
方法级别 @GetMapping("/find")
            =
最终 URL: /api/user/find

常见错误

  • 类级 URL 末尾加 / 导致路径重复
  • 方法级 URL 以 / 开头不影响最终路径,但容易造成风格不统一
  • 多个控制器映射到相同 URL 导致歧义

最佳实践

  • 统一以 REST 风格命名 API
  • URL 不加版本号,采用 /api/v1/user/* 风格

作者经验总结

企业项目应建立统一 API 命名规范,例如:

GET    /api/v1/users/{id}
POST   /api/v1/users
PUT    /api/v1/users/{id}
DELETE /api/v1/users/{id}

REST 命名让接口文档更标准可读。


四、请求参数相关注解

这一类注解是 Spring MVC 的灵魂,决定请求数据如何被映射到后端方法入参。


4.1 @RequestParam

原理

用于获取 URL 查询参数或表单参数。例如:

GET /user/search?name=tom&age=20

示例

@GetMapping("/search")
public String search(
        @RequestParam String name,
        @RequestParam(required = false, defaultValue = "18") Integer age
) {
    return "name=" + name + ", age=" + age;
}

常见错误

  • 传参名称不匹配导致 400 错误
  • 参数未标记 required=false 导致必须传参

作者经验总结

若参数带默认值,优先写在注解上而不是代码逻辑中,提高可维护性。


4.2 @PathVariable

原理

用于 REST 风格 URL 获取路径中变量。

例如:

GET /user/1/detail

示例

@GetMapping("/{id}/detail")
public User detail(@PathVariable("id") Long userId) {
    return new User(userId, "Tom");
}

注意事项

  • @PathVariable 名称必须一致,否则需手动指定 "value"
  • Spring 3.2+ 支持 PathVariable URI 模板正则表达式

示例:

@GetMapping("/order/{orderId:[0-9]+}")
public String order(@PathVariable Integer orderId) {
    return "OK";
}

4.3 @RequestBody

原理

从请求体中读取 JSON/XML 内容,并通过 HttpMessageConverter 反序列化成对象。

流程图:

请求 JSON → HttpMessageConverter → Java 对象

示例

@PostMapping("/create")
public User create(@RequestBody User user) {
    return user;
}

application/json 请求示例:

{
  "id": 1,
  "name": "Tom"
}

常见错误

  • 未开启对象序列化库(Spring Boot 默认开启 Jackson)
  • 未加 @RequestBody 导致参数为 null
  • Content-Type 设置不正确(必须 application/json

4.4 @ResponseBody

原理

将方法返回值序列化为 JSON(XML),返回给客户端。

@RestController 中已自动包含。

示例

@ResponseBody
@GetMapping("/info")
public User info() {
    return new User(1L, "Tom");
}

4.5 @RequestHeader

用于获取请求头参数。

@GetMapping("/ua")
public String ua(@RequestHeader("User-Agent") String ua) {
    return ua;
}

4.6 @CookieValue

用于获取 Cookie。

@GetMapping("/cookie")
public String cookie(@CookieValue("token") String token) {
    return token;
}

作者经验总结(本章节)

Spring MVC 参数解析由 HandlerMethodArgumentResolver 体系负责,掌握各类注解能避免大量手写 HttpServletRequest 的臃肿代码。建议保持 API 规范:“路径参数用 PathVariable,查询参数用 RequestParam,复杂对象用 RequestBody”。


五、MVC 高级注解

5.1 @ModelAttribute

原理

用于方法入参绑定,也可用于方法执行前向 Model 中填充数据。

用途

  • 绑定表单对象
  • 在所有请求前注入公共参数

示例 1:绑定表单对象

@PostMapping("/update")
public String update(@ModelAttribute User user) {
    return "ok";
}

表单提交数据会自动绑定到 User 对象。

示例 2:方法级提前注入 Model 数据

@ModelAttribute
public void common(Model model) {
    model.addAttribute("sysName", "UserCenter");
}

5.2 @SessionAttributes

用于将 model 中的指定属性放入 session。

@Controller
@SessionAttributes("user")
public class DemoController {

    @GetMapping("/set")
    public String set(Model model) {
        model.addAttribute("user", new User(1L, "Tom"));
        return "ok";
    }
}

5.3 @InitBinder

用于参数绑定控制,如日期格式化、字段过滤。

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(Date.class, new CustomDateEditor(
            new SimpleDateFormat("yyyy-MM-dd"), true));
}

作者经验总结(本章节)

多数企业项目不会大量使用 @ModelAttribute@SessionAttributes,但 @InitBinder 在处理自定义数据类型(金额、时间)时非常有用。


六、校验相关注解

Spring MVC 集成 Bean Validation(JSR-380),核心注解:

  • @Valid
  • @Validated

示例:

@PostMapping("/add")
public String add(@Validated @RequestBody User user) {
    return "ok";
}

配合实体类注解使用:

public class User {

    @NotNull
    private Long id;

    @NotBlank
    private String name;

    @Min(1)
    private Integer age;
}

全局异常处理(必备)

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handle(MethodArgumentNotValidException e) {
        return ResponseEntity.badRequest().body(e.getBindingResult().getFieldError().getDefaultMessage());
    }
}

七、全局异常处理注解

7.1 @ControllerAdvice

用于定义全局增强类,可进行:

  • 全局异常处理
  • 全局数据绑定
  • 全局绑定初始化

示例:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Map<String,Object> handler(Exception e){
        Map<String,Object> r = new HashMap<>();
        r.put("msg", e.getMessage());
        return r;
    }
}

八、数据序列化相关注解(Jackson)

  • @JsonIgnore
  • @JsonProperty
  • @JsonFormat

示例对象:

public class User {

    private Long id;

    @JsonProperty("userName")
    private String name;

    @JsonIgnore
    private String password;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
}

九、综合实战项目案例(Spring Boot)

下面提供一个完整可运行的 Spring Boot Demo,展示文章中的所有注解。


9.1 application.yml

server:
  port: 8080

spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

  jackson:
    time-zone: Asia/Shanghai
    serialization:
      indent-output: true

9.2 实体类

@Data
public class User {

    @NotNull
    private Long id;

    @NotBlank
    private String name;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
}

9.3 Controller

@RestController
@RequestMapping("/api/user")
public class UserController {

    @GetMapping("/{id}")
    public User find(@PathVariable Long id) {
        User u = new User();
        u.setId(id);
        u.setName("Tom");
        u.setCreateTime(LocalDateTime.now());
        return u;
    }

    @PostMapping("/create")
    public User create(@Valid @RequestBody User user) {
        user.setCreateTime(LocalDateTime.now());
        return user;
    }

    @GetMapping("/search")
    public String search(@RequestParam String name,
                         @RequestHeader("User-Agent") String ua) {
        return "name=" + name + ", ua=" + ua;
    }
}

9.4 全局异常处理

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> valid(MethodArgumentNotValidException e){
        String msg = e.getBindingResult().getFieldError().getDefaultMessage();
        return ResponseEntity.badRequest().body(msg);
    }
}

十、常见错误与 Debug 指南

错误:400 Bad Request

原因:

  • 参数类型无法转换
  • 缺少必要 @RequestParam
  • 未加 @RequestBody 导致请求体为空
  • JSON 不合法

错误:415 Unsupported Media Type

  • Content-Type 不是 application/json

错误:404

  • URL 不匹配
  • 类级 @RequestMapping 前缀写错

十一、最佳实践

  • API 分层规范
  • 注解使用一致性
  • 保持 JSON 格式标准
  • 所有参数校验必须显式写明
  • 异常使用统一处理机制
  • Controller 不写业务逻辑,只做参数解析与响应封装

十二、作者经验总结(全文)

Spring MVC 注解体系看似复杂,其实每一类都有明确职责:

  • 请求路由注解 决定 URL
  • 参数绑定注解 决定数据如何进入方法
  • 响应注解 决定数据如何返回
  • 校验注解 保证输入合法
  • 全局注解 提高可维护性

掌握这些注解,不仅是 Spring MVC 的基础,也是使用 Spring Boot 开发 REST API 的关键能力。在企业项目中,它们能极大简化 Controller 层,提高可读性和稳定性。


正文到此结束
评论插件初始化中...
Loading...