Spring MVC 深度解析
Spring MVC 是 Java Web 开发的事实标准。理解 DispatcherServlet 的请求处理流程,能帮你更好地处理各种 Web 场景。
请求处理流程
HTTP 请求
↓
DispatcherServlet(前端控制器)
↓
HandlerMapping(找到处理器)
- RequestMappingHandlerMapping(@RequestMapping)
↓
HandlerAdapter(适配器,调用处理器)
- RequestMappingHandlerAdapter
↓
HandlerInterceptor.preHandle()(拦截器前置)
↓
Controller 方法执行
- 参数解析(HandlerMethodArgumentResolver)
- 方法调用
- 返回值处理(HandlerMethodReturnValueHandler)
↓
HandlerInterceptor.postHandle()(拦截器后置)
↓
ViewResolver(视图解析,REST API 通常跳过)
↓
HandlerInterceptor.afterCompletion()
↓
HTTP 响应Controller 开发
java
@RestController // = @Controller + @ResponseBody
@RequestMapping("/api/v1/users")
@Validated // 开启方法级别参数验证
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
// GET /api/v1/users?page=1&size=20&keyword=alice
@GetMapping
public PageResult<UserVO> listUsers(
@RequestParam(defaultValue = "1") @Min(1) int page,
@RequestParam(defaultValue = "20") @Max(100) int size,
@RequestParam(required = false) String keyword) {
return userService.listUsers(page, size, keyword);
}
// GET /api/v1/users/42
@GetMapping("/{id}")
public UserVO getUser(@PathVariable @Positive Long id) {
return userService.getById(id)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在: " + id));
}
// POST /api/v1/users
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserVO createUser(@RequestBody @Valid CreateUserRequest req) {
return userService.create(req);
}
// PUT /api/v1/users/42
@PutMapping("/{id}")
public UserVO updateUser(
@PathVariable Long id,
@RequestBody @Valid UpdateUserRequest req) {
return userService.update(id, req);
}
// DELETE /api/v1/users/42
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
}请求参数绑定
java
@RestController
@RequestMapping("/demo")
public class BindingDemo {
// 路径变量
@GetMapping("/users/{id}/orders/{orderId}")
public String pathVar(@PathVariable Long id, @PathVariable Long orderId) {
return id + "/" + orderId;
}
// 查询参数
@GetMapping("/search")
public String queryParam(
@RequestParam String q,
@RequestParam(required = false, defaultValue = "10") int limit) {
return q + ":" + limit;
}
// 请求头
@GetMapping("/header")
public String header(
@RequestHeader("Authorization") String auth,
@RequestHeader(value = "X-Request-ID", required = false) String requestId) {
return auth;
}
// Cookie
@GetMapping("/cookie")
public String cookie(@CookieValue(value = "session", required = false) String session) {
return session;
}
// 请求体(JSON)
@PostMapping("/body")
public String body(@RequestBody Map<String, Object> data) {
return data.toString();
}
// 表单数据
@PostMapping("/form")
public String form(@ModelAttribute UserForm form) {
return form.toString();
}
// 文件上传
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) throws IOException {
String filename = file.getOriginalFilename();
file.transferTo(Path.of("/uploads/" + filename));
return "上传成功: " + filename;
}
// HttpServletRequest / HttpServletResponse(直接注入)
@GetMapping("/raw")
public void raw(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.getWriter().write("raw response");
}
}数据验证
java
// 请求 DTO
public class CreateUserRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 50, message = "用户名长度 2-50")
private String name;
@NotBlank
@Email(message = "邮箱格式不正确")
private String email;
@NotNull
@Min(value = 0, message = "年龄不能为负")
@Max(value = 150, message = "年龄不能超过 150")
private Integer age;
@NotBlank
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).{8,}$",
message = "密码至少 8 位,包含字母和数字")
private String password;
@Valid // 嵌套验证
@NotNull
private AddressRequest address;
}
// 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
// 参数验证失败
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleValidationException(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.toList());
return ApiResponse.fail(400, "参数验证失败", errors);
}
// 业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Void>> handleBusinessException(BusinessException ex) {
return ResponseEntity
.status(ex.getHttpStatus())
.body(ApiResponse.fail(ex.getCode(), ex.getMessage()));
}
// 资源不存在
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiResponse<Void> handleNotFound(ResourceNotFoundException ex) {
return ApiResponse.fail(404, ex.getMessage());
}
// 兜底异常
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiResponse<Void> handleException(Exception ex) {
log.error("未处理的异常", ex);
return ApiResponse.fail(500, "服务器内部错误");
}
}拦截器
java
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req,
HttpServletResponse resp,
Object handler) throws Exception {
// 放行静态资源和公开接口
if (!(handler instanceof HandlerMethod)) return true;
HandlerMethod method = (HandlerMethod) handler;
if (method.hasMethodAnnotation(PublicApi.class)) return true;
// 验证 Token
String token = req.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
resp.setStatus(401);
resp.getWriter().write("{\"code\":401,\"message\":\"未认证\"}");
return false;
}
Claims claims = jwtUtil.parseToken(token.substring(7));
req.setAttribute("userId", claims.getSubject());
return true;
}
@Override
public void afterCompletion(HttpServletRequest req,
HttpServletResponse resp,
Object handler, Exception ex) {
// 清理 ThreadLocal 等资源
RequestContext.clear();
}
}
// 注册拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**", "/api/public/**");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}统一响应格式
java
// 统一响应体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private long timestamp = System.currentTimeMillis();
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data, System.currentTimeMillis());
}
public static <T> ApiResponse<T> fail(int code, String message) {
return new ApiResponse<>(code, message, null, System.currentTimeMillis());
}
}
// 使用 ResponseBodyAdvice 统一包装
@RestControllerAdvice
public class ResponseWrapper implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
// 只包装 @RestController 的返回值
return returnType.getDeclaringClass().isAnnotationPresent(RestController.class)
&& !returnType.getParameterType().equals(ApiResponse.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof String) {
// String 类型需要特殊处理
return new ObjectMapper().writeValueAsString(ApiResponse.success(body));
}
return ApiResponse.success(body);
}
}Spring MVC 最佳实践
- 使用
@RestControllerAdvice统一处理异常,不要在 Controller 中 try-catch - 参数验证用
@Valid+ Bean Validation,不要手动 if 判断 - 返回
ResponseEntity可以精确控制 HTTP 状态码和响应头 - 文件上传配置
spring.servlet.multipart.max-file-size=50MB