Skip to content

Spring MVC 与 Web ​

Java 中级 · Spring 生态

Spring MVC 是 Spring 的 Web 层框架,核心是一个 DispatcherServlet(前端控制器) 和一套「组件协作」的请求处理流程。理解这九个组件如何串起来,你就能解释:为什么 @RequestMapping 能被识别、参数是怎么绑定的、拦截器和过滤器有什么区别、全局异常怎么统一处理。

一、请求处理全流程 ​

1.1 九大组件 ​

组件职责
HandlerMapping根据 URL 找 Handler 和拦截器
HandlerAdapter适配并执行 Handler
HandlerExceptionResolver异常解析(全局异常处理)
ViewResolver视图解析(JSP/Thymeleaf)
HandlerMethodArgumentResolver参数解析(@RequestParam 等)
HandlerMethodReturnValueHandler返回值处理
RequestToViewNameTranslatorURL 推导视图名
LocaleResolver国际化
MultipartResolver文件上传

二、请求映射与 RESTful ​

java
@RestController                       // = @Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")              // GET /api/users/1
    public User get(@PathVariable Long id) { return userService.get(id); }

    @GetMapping                       // GET /api/users?status=1
    public List<User> list(@RequestParam(required = false) Integer status) { /* ... */ }

    @PostMapping                      // POST /api/users
    public User create(@RequestBody @Valid User user) { /* ... */ }

    @PutMapping("/{id}")
    public void update(@PathVariable Long id, @RequestBody User user) { /* ... */ }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) { /* ... */ }
}
注解作用
@PathVariable取 URL 路径变量 /users/{id}
@RequestParam取查询参数 ?name=x
@RequestBody请求体 JSON → 对象
@RequestHeader请求头
@CookieValueCookie
@ModelAttribute表单绑定

参数绑定的本质

每个注解背后都有一个 HandlerMethodArgumentResolver。方法执行前,DispatcherServlet 会为每个参数找匹配的解析器,把 HTTP 请求里的数据「解析」成方法参数——这就是为什么参数能自动绑定。

三、参数校验 ​

java
public class User {
    @NotBlank(message = "用户名不能为空")
    private String name;

    @Min(value = 18, message = "年龄不能小于18")
    private Integer age;

    @Email(message = "邮箱格式错误")
    private String email;
}

@PostMapping
public User create(@RequestBody @Valid User user, BindingResult result) {
    if (result.hasErrors()) {
        throw new IllegalArgumentException(result.getFieldError().getDefaultMessage());
    }
    return userService.save(user);
}

四、统一异常处理 ​

java
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusiness(BusinessException e) {
        return Result.fail(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValid(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldError().getDefaultMessage();
        return Result.fail(400, msg);
    }

    @ExceptionHandler(Exception.class)          // 兜底
    public Result<Void> handleAll(Exception e) {
        log.error("系统异常", e);
        return Result.fail(500, "系统繁忙,请稍后再试");
    }
}

异常处理要分层

  • 业务异常(如「库存不足」)返回明确提示,HTTP 200 + 业务码 或 4xx。
  • 系统异常(NPE、超时)返回通用提示,不能把堆栈暴露给前端,但必须完整记录日志。
  • 兜底的 Exception 处理器一定要有,否则异常会以默认错误页返回,体验差且可能泄露信息。

五、拦截器 vs 过滤器 ​

对比FilterInterceptor
规范ServletSpring MVC
作用范围所有请求(含静态资源)只拦 Controller 请求
能拿到什么ServletRequestHandlerMethod(知道调用哪个方法/注解)
典型用途编码、跨域、XSS、日志埋点登录校验、权限、限流
java
@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
        if (req.getHeader("token") == null) {
            resp.setStatus(401);
            return false;      // 返回 false 中断请求
        }
        return true;
    }
}

// 注册
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/login");
    }
}

六、跨域(CORS) ​

java
// 方式一:全局配置(推荐)
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOriginPatterns("*")     // 生产要写具体域名!
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

生产别用 allowedOrigins("*") + allowCredentials(true)

二者不能同时生效(浏览器会拒绝)。且 * 允许任意站点携带凭证请求,存在 CSRF 风险。生产必须白名单具体域名。

七、最佳实践 ​

  • Controller 只做参数校验和编排,业务逻辑放 Service。
  • 统一返回结构(Result<T>),前端处理一致。
  • 全局异常处理器兜底,别让异常裸奔到前端。
  • 登录校验用拦截器、编码/跨域用过滤器,各司其职。
  • CORS 白名单化,别图省事用 *。
  • RESTful 语义规范:GET 查询、POST 新增、PUT 全量改、PATCH 局部改、DELETE 删除。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 画出 DispatcherServlet 处理一次请求的完整流程,指出拦截器三个方法的调用时机。
  2. @PathVariable、@RequestParam、@RequestBody 分别从请求的哪个位置取值?
  3. Filter 和 Interceptor 有什么区别?登录校验、跨域分别该用哪个?
  4. 写一个全局异常处理器,分别处理业务异常、参数校验异常和系统异常。

本节小结

Spring MVC 的核心是 DispatcherServlet + 九大组件:HandlerMapping 找方法、HandlerAdapter 执行、ArgumentResolver 绑参数、HandlerExceptionResolver 统一处理异常。拦截器能感知 Handler、过滤器不能;异常处理要分层,兜底处理器不能少;CORS 生产必须白名单。