🌐 Spring MVC
DispatcherServlet 请求流程 · 参数绑定 · 拦截器 vs 过滤器 · 统一异常处理 · RESTful
1. 一次请求在 Spring MVC 中的完整处理流程?(必背)
- 请求到达 DispatcherServlet(前端控制器,所有请求入口)
- DispatcherServlet 调用 HandlerMapping 找到匹配的 Handler(Controller 方法)及其拦截器链
- 通过 HandlerAdapter 执行 Handler(适配器模式,兼容不同签名);执行前拦截器 preHandle
- HandlerAdapter 内部:参数解析(HandlerMethodArgumentResolver)→ 调用方法 → 返回值处理(HandlerMethodReturnValueHandler)
- 返回 ModelAndView(视图名 + 模型)或直接 @ResponseBody(MessageConverter 序列化 JSON)
- 拦截器 postHandle(此时视图还没渲染)→ ViewResolver 解析视图名 → 渲染(无 @ResponseBody 时)
- 响应返回客户端;拦截器 afterCompletion(finally 语义)
核心组件速记
DispatcherServlet // 前端控制器:调度中枢
HandlerMapping // 请求 → 处理器 的映射(RequestMappingHandlerMapping)
HandlerAdapter // 适配执行处理器(RequestMappingHandlerAdapter)
HandlerMethodArgumentResolver // 方法参数解析器(@RequestParam/@RequestBody/模型对象)
HandlerMethodReturnValueHandler // 返回值处理器(@ResponseBody → Jackson)
ViewResolver // 视图解析(Thymeleaf/JSP)
MessageConverter // HTTP 消息转换(Jackson 等)
🎯 面试要点
- DispatcherServlet 本质是一个 Servlet,注册在 Servlet 容器(内嵌 Tomcat)中,映射 "/"
- 适配器模式:DispatcherServlet 不直接调用 Controller,避免与具体实现耦合
- @ResponseBody 不经过视图解析,直接走 HttpMessageConverter 序列化
2. 常用参数绑定注解?POST JSON 怎么接?
- @PathVariable:URL 路径参数
/user/{id} - @RequestParam:查询参数/表单字段,required 控制必填
- @RequestBody:请求体 JSON → 对象(Jackson 反序列化)
- @RequestHeader:请求头;@CookieValue:Cookie
- @ModelAttribute:表单/查询参数绑定到模型对象(对象字段自动映射)
- 无注解 POJO 参数:默认按字段名绑定到模型
典型 Controller
@RestController
class UserController {
@GetMapping("/user/{id}")
public User get(@PathVariable Long id) { ... }
@PostMapping("/user")
public User create(@Valid @RequestBody User user) { ... }
// @Valid 触发 JSR-303 校验(@NotNull/@Email 等),失败抛 MethodArgumentNotValidException
@GetMapping("/user")
public Page list(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) { ... }
}
🎯 面试要点
- @RequestBody 只能有一个;Content-Type 需为 application/json
- 参数校验:@Valid + 全局异常捕获 BindingResult/MethodArgumentNotValidException
- @RestController = @Controller + @ResponseBody(每个方法都返回 JSON)
3. 拦截器(Interceptor)和过滤器(Filter)的区别?
| 对比 | Filter | Interceptor |
|---|---|---|
| 规范 | Servlet 规范 | Spring 框架 |
| 作用范围 | 所有请求(含静态资源、Servlet) | 仅经过 DispatcherServlet 的 Controller 请求 |
| 执行时机 | Servlet 之前 | Handler 执行前后(有 preHandle/postHandle/afterCompletion) |
| 依赖 | 不依赖 Spring(web.xml 注册) | 依赖 Spring IoC,可注入 Bean |
选择:全局性、与框架无关的(编码、跨域、认证)用 Filter;需要 Spring Bean、按 URL 规则精细化拦截(登录校验、权限、审计)用 Interceptor。
拦截器注册
@Configuration
class WebConfig implements WebMvcConfigurer {
@Override public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login", "/api/register");
}
}
🎯 面试要点
- 执行顺序:Filter → DispatcherServlet → Interceptor.preHandle → Controller → postHandle → afterCompletion → Filter 返回
- preHandle 返回 false 请求终止;登录态校验常放 Interceptor
- 跨域(CORS)Filter 要放最外层,否则预检 OPTIONS 请求会被鉴权 Filter/拦截器拦掉或重定向,浏览器就拿不到 Access-Control-Allow-* 响应头
4. 全局异常处理怎么做?
- @RestControllerAdvice(= @ControllerAdvice + @ResponseBody):全局异常切面类
- @ExceptionHandler:指定异常类型 → 统一响应格式(ResponseEntity + 错误码/消息)
- 优先级:方法内 try-catch > 类内 @ExceptionHandler > 全局 @RestControllerAdvice > 框架默认
统一异常返回模板
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class) // 业务异常
public Result<Void> biz(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class) // 参数校验失败
public Result<Void> valid(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage).collect(Collectors.joining(";"));
return Result.error(400, msg);
}
@ExceptionHandler(Exception.class) // 兜底,记录日志,不泄露堆栈给前端
public Result<Void> other(Exception e) {
log.error("unexpected", e);
return Result.error(500, "系统繁忙");
}
}
🎯 面试要点
- 业务异常统一一个 BizException,配合全局处理器,Controller 里不用到处 try-catch
- 兜底 Exception 一定要 log.error,否则线上问题无从查起
- 404/401 等也可以自定义 ErrorController / 处理 NoHandlerFoundException
5. RESTful 风格要点?
- 资源 + 动词:URL 只表示资源(名词复数),操作由 HTTP 方法表达 —— GET 查 / POST 增 / PUT 全量改 / PATCH 部分改 / DELETE 删
- 状态码表达结果:200 成功、201 创建、400 参数错、401 未认证、403 无权限、404 不存在、500 服务错
- 无状态:服务端不保存会话(JWT 等令牌认证)
- 幂等性:GET/PUT/DELETE 幂等,POST 不幂等
🎯 面试要点
- 注意与 RPC 风格对比(URL 含动作如 /user/getUser):REST 是资源导向
- Spring 中 @GetMapping("/users/{id}") 等组合注解即 REST 风格