Spring 控制器 @Controller 与 @RestController 全解析(2025 版)
恭喜你继续深挖 Spring MVC!前面讲了 DispatcherServlet,这次专攻控制器(Controller),让你彻底搞懂 @Controller 和 @RestController 的区别、使用场景、源码原理、常见坑,以及 2025 年最新最佳实践。内容实用、可直接复制代码到项目。
一、核心区别一览表(面试必背)
| 项目 | @Controller | @RestController |
|---|---|---|
| 注解组成 | 仅 @Component | @Controller + @ResponseBody |
| 方法返回值默认处理 | String → 视图名(跳转页面) | 任意对象 → 直接转 JSON 返回 |
| 适用场景 | 返回 HTML 页面(Thymeleaf、JSP) | 返回 JSON(前后端分离、移动端 API) |
| 是否需要 @ResponseBody | 每个方法都需要手动加 | 全类自动生效,无需加 |
| Spring Boot 默认推荐 | 传统网页项目 | 99% 的现代项目(API 接口) |
| 常见搭配 | @RequestMapping + Model | @GetMapping + POJO/DTO |
结论:如果你是写 API 接口,直接用 @RestController,一劳永逸。
只有需要返回网页模板时才用 @Controller。
二、基本使用示例(Spring Boot 3.x,直接复制运行)
// 1. @RestController 示例(推荐,99% 项目用这个)
@RestController
@RequestMapping("/api/users")
public class UserApiController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// 直接返回对象,自动转 JSON: {"id":1,"name":"张三"}
return new User(id, "张三");
}
@PostMapping
public User create(@RequestBody User user) {
// 自动把 JSON 转成 User 对象
return userService.save(user);
}
@GetMapping
public List<User> list() {
return userService.list();
}
}
// 2. @Controller 示例(返回网页)
@Controller
@RequestMapping("/page")
public class PageController {
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id, Model model) {
model.addAttribute("user", userService.get(id));
// 返回视图名,自动去找 /templates/user/detail.html
return "user/detail";
}
// 如果某个方法想返回 JSON,必须手动加 @ResponseBody
@GetMapping("/user/json/{id}")
@ResponseBody
public User getUserJson(@PathVariable Long id) {
return userService.get(id);
}
}
三、@RestController 内部到底是怎么实现的?(源码解析)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody // ← 关键就在这里!
public @interface RestController {
// ...
}
- DispatcherServlet 调用你的方法后
- 看到类上有 @ResponseBody → 使用 HttpMessageConverter(默认 Jackson)把返回值转成 JSON 并写入 response body
- 没有 @ResponseBody → 当成视图名,去 ViewResolver 找模板文件
四、常用请求映射注解(Spring Boot 提供,超级好用)
@RestController
@RequestMapping("/api/books")
public class BookController {
@GetMapping("/{id}") // GET /api/books/1
public Book get(@PathVariable Long id) { ... }
@PostMapping // POST /api/books
public Book create(@RequestBody Book book) { ... }
@PutMapping("/{id}") // PUT /api/books/1
public Book update(@PathVariable Long id, @RequestBody Book book) { ... }
@DeleteMapping("/{id}") // DELETE /api/books/1
public void delete(@PathVariable Long id) { ... }
@PatchMapping("/{id}") // PATCH /api/books/1
public Book patch(...) { ... }
}
五、方法参数与返回值高频用法(30+ 种参数解析器)
@GetMapping("/search")
public List<User> search(
@RequestParam String name, // ?name=张三
@RequestParam(required = false) Integer age, // 可选参数
@RequestParam(defaultValue = "1") int page, // 默认值
HttpServletRequest request, // 原生对象
@CookieValue("token") String token, // Cookie
@RequestHeader("User-Agent") String ua) { // Header
...
}
返回值常见类型(@RestController):
- POJO → JSON 对象
- List → JSON 数组
- Map → JSON 对象
- String → 纯文本(很少用)
- ResponseEntity → 可自定义状态码和头
- void → 状态 200(常配合 @ResponseStatus)
六、生产级最佳实践(2025 年推荐)
- 统一响应格式(所有接口返回一致结构)
// 推荐返回类
public class R<T> {
private int code;
private String msg;
private T data;
public static <T> R<T> ok(T data) { ... }
public static <T> R<T> error(String msg) { ... }
}
// 使用
@GetMapping("/{id}")
public R<User> get(@PathVariable Long id) {
User user = userService.get(id);
return R.ok(user);
}
- 全局异常处理(别让异常直接返回 500)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public R<String> handle(Exception e) {
return R.error("服务器异常:" + e.getMessage());
}
}
- 参数校验(JSR-303)
@PostMapping
public R<User> create(@Valid @RequestBody User user) { ... }
// User 类
public class User {
@NotBlank(message = "姓名不能为空")
private String name;
@Email(message = "邮箱格式错误")
private String email;
}
- 跨域处理(前后端分离必备)
// 全局跨域
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*");
}
}
七、常见坑 & 避坑指南
- 坑1:用了 @Controller 但忘记加 @ResponseBody → 返回视图名,导致 404
解决:改成 @RestController - 坑2:@RequestMapping 在类和方法上重复写路径
推荐:类上写共同前缀,方法上只写剩余部分(如上例) - 坑3:返回值是 null → JSON 返回 null 或空对象
解决:Jackson 配置 @JsonInclude(Include.NON_NULL) - 坑4:大文件上传报错
解决:在 application.yml 配置
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
八、总结推荐(2025 年)
| 项目场景 | 推荐注解 | 理由 |
|---|---|---|
| 前后端分离 API | @RestController | 直接返回 JSON,简洁 |
| 传统网页项目 | @Controller | 返回 Thymeleaf/JSP 页面 |
| 混合项目(既有页面又有API) | 两个都用,分开写 Controller | 清晰分离 |
一句话口诀:
写接口用 @RestController,返回页面用 @Controller。
99% 的新项目直接全用 @RestController + 统一响应体 R 就够了!
你现在可以直接复制上面的代码创建一个完整的 CRUD 接口项目跑起来!
下一步想学什么?
- 统一响应体 + 全局异常处理(完整代码)
- 参数校验 @Valid + 自定义校验器
- 分页查询(PageHelper 或 MyBatis-Plus)
- Swagger/OpenAPI 文档自动生成
- 控制器单元测试(@WebMvcTest)
直接告诉我,我继续给你超详细的实战教程!