Controller
本章进入 Spring MVC 的控制器层。DispatcherServlet 负责接收和分发请求,HandlerMapping 负责查找处理器,但真正的业务逻辑入口是 Controller。
@Controller是 Spring MVC 中最基础的注解之一,它标记一个类为 Web 请求处理器,其方法返回值在默认情况下会被 ViewResolver 解析为视图名。理解@Controller,就理解了 Spring MVC 的视图渲染模式。
定义与作用
@Controller 是 Spring Framework 提供的类级注解,它的职责可以用一句话概括:标记一个类为 Spring MVC 的控制器,使其方法能够处理 HTTP 请求,并将方法返回值作为视图名进行解析。
在 Spring MVC 的组件体系中:
- @Controller = Web 层组件,负责接收请求、调用业务层、返回视图或数据
- @Service = 业务层组件,负责业务逻辑(本教程不展开)
- @Repository = 数据层组件,负责数据访问(本教程不展开)
@Controller 与 @Service 的区别
| 对比维度 | @Controller | @Service |
|---|---|---|
| 所属层级 | Web 层(表现层) | 业务层 |
| 核心职责 | 接收 HTTP 请求、调用 Service、返回响应 | 执行业务逻辑、事务管理 |
| 方法返回值 | 默认被 ViewResolver 解析为视图名 | 业务对象或原始数据 |
| 是否感知 HTTP | 是(操作 Request/Response) | 否(纯业务逻辑) |
| 典型方法 | String listUsers() | List<User> findAll() |
| 是否可被注入 | 是(容器管理) | 是(容器管理) |
关键边界:@Controller 的方法默认走视图渲染流程——返回 "employee/list" 时,ViewResolver 会查找 /WEB-INF/views/employee/list.jsp(或 Thymeleaf 模板)。如果希望方法返回值直接写入响应体(如 JSON),需要额外标注 @ResponseBody。
核心原理
@Controller 的识别与注册
图解:
@Controller类在启动时被注册为 Bean,同时其@RequestMapping方法被解析为映射规则- 请求到达时,HandlerAdapter 调用 Controller 方法
- 没有
@ResponseBody时,返回值走 ViewResolver 流程(视图渲染模式) - 有
@ResponseBody时,返回值走 HttpMessageConverter 流程(数据直写模式)
适用位置与常用属性
@Controller 是元注解 @Component 的特化,本身没有额外属性,但通常与以下注解配合使用:
配合使用的注解
| 注解 | 作用 | 与 @Controller 的关系 |
|---|---|---|
@RequestMapping | 声明请求映射规则 | 标注在 @Controller 类的方法上 |
@ResponseBody | 返回值直接写入响应体 | 标注在方法上,绕过视图解析 |
@RestController | @Controller + @ResponseBody | 类级组合注解,所有方法默认直写 |
@Autowired | 依赖注入 | 注入 Service Bean(容器已提供) |
Controller 方法返回值类型对比
| 返回值类型 | 处理方式 | 典型场景 |
|---|---|---|
String | 作为视图名解析 | 返回 "employee/list" → 渲染列表页 |
ModelAndView | 同时携带视图名和数据模型 | 需要显式控制视图和模型 |
void | 无视图,由方法自行处理响应 | 直接操作 HttpServletResponse |
View | 直接返回视图对象 | 需要动态选择视图实现 |
Map / Model | 作为模型数据,视图名由 RequestToViewNameTranslator 推断 | 简化返回值 |
任意对象(+ @ResponseBody) | HttpMessageConverter 序列化 | RESTful API 返回 JSON/XML |
完整示例
场景
飞翔科技的员工管理系统需要支持页面渲染模式。前端工程师黄俪使用 Thymeleaf 模板引擎,后端工程师小崔编写 @Controller 返回视图名。
控制器代码
package com.feixiang.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/employees")
public class EmployeeController {
// 容器已注入该 Bean,本教程不展开 Service 实现
@Autowired
private EmployeeService employeeService;
// 示例 1:返回 String 作为视图名
@GetMapping
public String listAll(Model model) {
model.addAttribute("employees", employeeService.findAll());
return "employee/list"; // 解析为 /templates/employee/list.html
}
// 示例 2:返回 ModelAndView
@GetMapping("/{id}/detail")
public ModelAndView getDetail(@PathVariable Long id) {
ModelAndView mav = new ModelAndView("employee/detail");
mav.addObject("employee", employeeService.findById(id));
return mav;
}
}
HTTP 请求示例 1:返回 String 视图名
curl -X GET http://localhost:8080/employees
实际响应(Thymeleaf 渲染后的 HTML):
<!DOCTYPE html>
<html>
<head><title>员工列表</title></head>
<body>
<h1>飞翔科技员工列表</h1>
<table>
<tr><th>ID</th><th>姓名</th><th>部门</th></tr>
<tr><td>1001</td><td>张伟</td><td>技术部</td></tr>
<tr><td>1002</td><td>李娜</td><td>市场部</td></tr>
</table>
</body>
</html>
分析:
listAll()返回"employee/list"- ViewResolver(ThymeleafViewResolver)解析为
classpath:/templates/employee/list.html Model中的employees数据被填充到模板中- 最终渲染为完整 HTML 返回给浏览器
HTTP 请求示例 2:返回 ModelAndView
curl -X GET http://localhost:8080/employees/1001/detail
实际响应:
<!DOCTYPE html>
<html>
<head><title>员工详情</title></head>
<body>
<h1>员工详情</h1>
<p>ID:1001</p>
<p>姓名:张伟</p>
<p>部门:技术部</p>
<p>职位:高级工程师</p>
</body>
</html>
分析:
getDetail()返回ModelAndView,同时携带视图名和数据- 相比返回
String,ModelAndView更适合需要显式控制视图和模型的场景 - 两种方式的最终渲染结果完全一致
易错场景与面试考点
误区一:@Controller 方法返回对象时自动变成 JSON
错误认知:"@Controller 的方法返回 Employee 对象,浏览器会自动看到 JSON。"
纠正:@Controller 的方法返回值默认作为视图名。返回 Employee 对象时,Spring MVC 会尝试把它当作视图名解析,结果通常是 404(找不到名为 com.feixiang.Employee 的视图)。
正确做法:
// 方式一:方法级 @ResponseBody
@GetMapping("/{id}")
@ResponseBody
public Employee getById(@PathVariable Long id) {
return employeeService.findById(id);
}
// 方式二:使用 @RestController(推荐)
@RestController
public class EmployeeController {
@GetMapping("/{id}")
public Employee getById(@PathVariable Long id) {
return employeeService.findById(id);
}
}
误区二:@Controller 和 @Component 没有区别
错误认知:"@Controller 就是 @Component 换个名字,随便用哪个都一样。"
纠正:虽然 @Controller 元注解包含 @Component,但 Spring MVC 对 @Controller 有额外处理:
RequestMappingHandlerMapping只扫描@Controller(或@RestController)类中的@RequestMapping方法- 标注
@Component的类即使有@RequestMapping方法,也不会被注册为请求处理器 @Controller还参与 Spring MVC 的异常处理和视图解析流程
面试高频:@Controller 和 @RestController 的区别
标准回答:
@Controller是 Spring MVC 的 Web 层组件注解,方法返回值默认作为视图名由 ViewResolver 解析@RestController是@Controller + @ResponseBody的组合注解,所有方法返回值默认通过 HttpMessageConverter 序列化写入响应体- 开发传统服务器渲染页面(如 JSP/Thymeleaf)时使用
@Controller - 开发 RESTful API(返回 JSON/XML)时使用
@RestController
小结
@Controller 是 Spring MVC 的 Web 层入口注解,它标记的类会被 Spring MVC 识别为请求处理器,其方法返回值默认作为视图名走 ViewResolver 解析流程。它是视图渲染模式的基石,与 @RestController 的数据直写模式形成互补。
本章与全局的关系:本章讲解了 @Controller 的视图渲染模式。下一章"RestController"将讲解 @RestController 的数据直写模式,以及两者的选择策略。