Spring MVC 请求生命周期
SpringBoot 2.7 + Java8 企业级开发
一、整体架构图 —— 一次请求的完整流水线
HTTP Request -> http://你的项目/getUser?id=1
│
▼
┌─────────────────────┐
│ Tomcat │ 内嵌 Servlet 容器,最早接收请求
│ Servlet Container │
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Filter 过滤器链 │ Servlet 级别的“门卫”,工作在 Spring MVC 之前
└─────────────────────┘
│
▼
┌─────────────────────┐
│ DispatcherServlet │ Spring MVC 的“总控”,分发请求的核心
└─────────────────────┘
│
▼
┌─────────────────────┐
│ HandlerInterceptor │ Spring 自己的拦截器,更懂 Controller
│ preHandle │ 请求到达 Controller 前执行
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Controller │ 你写的接口,@RestController / @GetMapping
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Service │ 业务逻辑层
└─────────────────────┘
│
▼
┌─────────────────────┐
│ DAO/JdbcTemplate │ 数据访问层,执行 SQL
└─────────────────────┘
│
▼
┌─────────────────────┐
│ MySQL │ 数据库
└─────────────────────┘
│
▼
Controller返回结果 返回 Java 对象
│
▼
Interceptor.postHandle 所有拦截器的 postHandle
│
▼
ViewResolver / JSON序列化 前后端分离时转成 JSON 字符串
│
▼
Interceptor.afterCompletion 资源清理、日志记录
│
▼
Response → 浏览器二、企业项目真实执行顺序(必背)
虚假的顺序:
Tomcat -> Interceptor -> Controller实际严格顺序:
Tomcat -> Filter -> DispatcherServlet -> Interceptor -> Controller -> Service -> DAO -> MySQL为什么是这个顺序?
- Filter 是 Servlet 规范 定义的,工作在容器级别,比 Spring 更底层;
- Interceptor 是 Spring MVC 内部 的,只有在 DispatcherServlet 接收请求之后才生效。
- 也就是说:Filter 可以拦截所有请求(包括静态资源),而 Interceptor 只能拦截进入 Spring MVC 的请求。
三、Filter 和 Interceptor 区别 —— 两个不同级别的“保安”
1. Filter(Servlet 级别)
- 定义方式:实现
javax.servlet.Filter接口 - 执行时机:
Tomcat → Filter → Spring MVC - 特点:工作在容器层,不依赖 Spring,拿不到 Controller、方法等 Spring 上下文信息。
典型用途:
- JWT 登录校验(直接解析 token,失败直接返回 401)
- XSS 过滤(清洗
<script>等危险字符) - 请求日志(记录每个请求的耗时、URL)
跨域处理(
CorsFilter统一设置响应头)2. Interceptor(Spring MVC 级别)
- 定义方式:实现
org.springframework.web.servlet.HandlerInterceptor接口 - 执行时机:
DispatcherServlet → Interceptor → Controller - 特点:能获取到 Handler(Controller 方法)、参数、返回结果等,是 Spring 生态内的拦截手段。
典型用途:
- 用户信息注入(从 token 解析用户信息,放入
UserContext) - 权限校验(admin / user 角色判断)
- 操作日志(记录“谁在何时做了什么”)
API 访问统计(PV / UV 计数)
面试标准答案
Filter 先执行,Interceptor 后执行。 Filter 是 Servlet 规范,Intercetpor 是 Spring MVC 专属。四、DispatcherServlet 到底干了什么 —— 核心方法 doDispatch()
设计模式:前端控制器模式(Front Controller Pattern)
所有请求都会先被 DispatcherServlet 接管,它自己不干活,只负责指挥。
源码简化流程:doDispatch() { // 1. 根据请求找到 Handler(Controller 方法)并包装成 HandlerExecutionChain HandlerExecutionChain chain = getHandler(request); // 2. 执行所有拦截器的 preHandle chain.applyPreHandle(request, response); // 3. 真正调用 Controller 方法 HandlerAdapter adapter = getHandlerAdapter(handler); ModelAndView mv = adapter.handle(request, response, handler); // 4. 执行所有拦截器的 postHandle chain.applyPostHandle(request, response, mv); // 5. 处理返回结果(JSON 序列化、视图渲染等) processDispatchResult(request, response, chain, mv, exception); // 6. 触发拦截器的 afterCompletion(总是执行) chain.triggerAfterCompletion(request, response, exception); }五、HandlerExecutionChain —— Controller + 拦截器的组合体
请求到达 DispatcherServlet 后,
getHandler()方法会将你的 Controller 方法和所有适用的拦截器打包成一个执行链。
结构示意:HandlerExecutionChain ├── Handler(你的 Controller 方法) ├── Interceptor A ├── Interceptor B └── Interceptor C执行顺序(链式调用,具有栈特性):
- 用户信息注入(从 token 解析用户信息,放入
- 进入时:
A.pre → B.pre → C.pre → Controller - 返回时:
C.post → B.post → A.post 最终完成:
C.after → B.after → A.after(即使前面出异常也会执行)六、@RequestScope 的底层原理 —— 代理对象,而非每次都 new
很多人以为
@RequestScope每次请求都会创建一个全新的 Bean,实际上 Spring 注入的是一个代理对象。
特别提醒:
代理对象通过当前线程的 ThreadLocal 拿到绑定的请求上下文,从而找到属于当前请求的那个真实 @RequestScope 实例。
如果在 Controller 里用 @Async 等开了新线程,原请求的 ThreadLocal 会丢失,导致代理找不到请求上下文而直接报错。
@Autowired
private UserContext userContext;实际注入的是 UserContext$$SpringProxy 代理类。该代理对象内部并不持有真实数据,当真正调用其方法时,它会从当前请求的 RequestAttributes 中动态获取属于当前请求的真实实例。
流程:
当前线程 → RequestAttributes → 真实的 UserContext好处:即便 Controller 是单例(Singleton),也能安全地使用 @RequestScope 的 Bean,因为每次调用都会通过代理去获取当前请求对应的真实对象。
七、ThreadLocal 与 @RequestScope —— 如何安全地存放用户信息
在企业项目中,经常需要在任何地方(Service、DAO)获取当前登录用户,常用两种方案。
| 方案 | 实现方式 | 优点 | 缺点 | 最佳实践 |
|---|---|---|---|---|
| ThreadLocal | UserHolder.set(user) / UserHolder.get() | 极快,线程隔离 | 必须手动 remove(),否则线程池复用时会内存泄漏或数据串人 | 在拦截器的 afterCompletion 中清理 |
| @RequestScope | 注解在 Spring 管理的 Bean 上 | Spring 自动管理生命周期,请求结束自动销毁,无泄漏风险 | 性能比 ThreadLocal 多一点点反射/代理开销 | 优先推荐,简单安全 |
推荐:存放用户上下文信息时,优先选择 @RequestScope。只有在对性能要求极高、并发极大的场景下,才考虑 ThreadLocal,并确保在拦截器的 afterCompletion 中执行 remove()。
八、Controller 参数解析过程 —— 为什么参数能自动绑定
你写的 public Result get(Long id),Spring 能自动从请求里拿到 id,靠的是 HandlerMethodArgumentResolver 接口。
该解析器会遍历判断当前参数是否能被自己解析,若能则从 request 中取值并绑定。
常见注解与对应解析器:
| 注解 | 解析器 |
|---|---|
@RequestParam | RequestParamMethodArgumentResolver |
@PathVariable | PathVariableMethodArgumentResolver |
@RequestBody | RequestResponseBodyMethodProcessor |
@RequestHeader | RequestHeaderMethodArgumentResolver |
例如:@RequestHeader("token") String token 背后就是 RequestHeaderMethodArgumentResolver 在干活。
你也可以实现该接口并注册,来支持自定义的参数注入(比如自动注入当前登录用户对象)。
九、JSON 如何变成 Java 对象 —— HttpMessageConverter
前端发送 JSON:
POST
{ "name":"Tom" }到后端自动变成 UserDTO 对象,核心组件是 HttpMessageConverter。
默认使用的实现是 MappingJackson2HttpMessageConverter,底层依赖 Jackson。
请求方向:
请求体 JSON → Jackson 反序列化 → Java 对象 → Controller 参数响应方向:
Controller 返回的 Java 对象 → Jackson 序列化 → JSON 字符串 → HTTP 响应因此 Jackson 是 Spring Boot 中处理 JSON 的事实标准,排查问题时经常要关注它。
十、统一异常处理 —— 别再到处 try-catch
在企业项目中,应使用 @RestControllerAdvice 进行全局异常拦截,避免在每个 Controller 方法中写重复的 try-catch。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error("系统异常", e);
return Result.fail(e.getMessage());
}
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException e) {
return Result.fail(e.getCode(), e.getMessage());
}
}执行流程:
Controller 抛出异常
↓
HandlerExceptionResolver 捕获
↓
找到匹配的 @ExceptionHandler 方法
↓
返回统一的错误响应这样 Controller 只需关注正常业务逻辑,所有异常都会流入全局处理器,返回规范的接口格式。
十一、完整认证流程 —— 不要只相信 Token
真正的安全认证绝不能只解析 token,而必须结合 Redis 等存储做二次校验。
标准流程:
1. 请求携带 JWT token
2. Interceptor 解析 token 得到 userId
3. 去 Redis 查询该 userId 对应的完整用户信息(含权限、状态)
4. 如果 Redis 中不存在或 token 已失效 → 直接返回 401
5. 将用户信息放入 UserContext(@RequestScope 管理的 Bean)
6. Controller 直接从 UserContext 获取用户信息,无需再解析 token为什么要加 Redis?
- Token 可能被伪造(弱签名或密钥泄露)
- 用户可能已被管理员禁用,但 token 还未过期
用户执行了“退出登录”,token 理应立刻失效
总结:Token 只负责提供身份标识(userId),真正的用户资料和权限应从 Redis(或数据库)获取,并在请求上下文中传递。
十二、Spring MVC 八大核心组件(高级理解)
DispatcherServlet 只是总控,真正复杂的工作由这些组件完成:
组件 作用 重要性 HandlerMapping 根据请求 URL 找到对应的 Handler(Controller 方法) ★★★ HandlerAdapter 真正执行 Handler,适配不同类型的处理器 ★★★ HandlerExceptionResolver 处理异常,分发给 @ExceptionHandler 或 @ControllerAdvice ★★★ ViewResolver 解析视图名,找到具体视图(前后端分离时用得少) ★ MultipartResolver 处理文件上传请求 ★ LocaleResolver 国际化,解析客户端的语言/地区 ★ ThemeResolver 主题切换(老旧项目中常见) ★ FlashMapManager 用于重定向时保存临时数据 ★
其中最核心的三个:HandlerMapping、HandlerAdapter、HandlerExceptionResolver,理解它们才能真正把握 Spring MVC 的执行原理。
总结 —— 一条线全部串起来
完整请求处理链路:
Tomcat
→ Filter(登录校验、XSS、日志、跨域)
→ DispatcherServlet
→ HandlerMapping(找到 Controller 方法)
→ HandlerInterceptor.preHandle(权限、用户注入)
→ HandlerAdapter(执行方法)
→ Controller
→ Service
→ DAO / JdbcTemplate
→ MySQL
→ (若有异常)HandlerExceptionResolver → @RestControllerAdvice 统一处理
→ HttpMessageConverter(Java 对象 ↔ JSON)
→ HandlerInterceptor.postHandle / afterCompletion
→ 返回 Response日常企业开发(SpringBoot 2.7 + JdbcTemplate + MySQL + Redis + JWT)中最核心的10个知识点:
- Filter — 容器层拦截,适合安全校验、跨域、日志
- Interceptor — Spring MVC 拦截,适合用户注入、权限、统计
- @RequestScope — 请求作用域 Bean,底层用代理实现
- ThreadLocal — 线程隔离的上下文存储,需手动清理
- HandlerMethodArgumentResolver — 参数解析器,实现自动注入
- HttpMessageConverter — JSON / 对象互转,底层 Jackson
- @RestControllerAdvice — 全局异常处理,不要到处 try-catch
- DispatcherServlet — 前端总控制器,调度所有请求
- HandlerExecutionChain — 拦截器与 Controller 的打包执行链
- Spring MVC 九大组件 — 理解项目架构离不开这些支撑组件
评论