MVC
Spring MVC 是 Spring 的 Web 框架,基于前端控制器模式,由 DispatcherServlet 统一接收请求并分发给相应的 Controller。Spring Boot 通过自动配置开箱即用。
请求处理流程
客户端请求
└─► DispatcherServlet
├─► HandlerMapping(找到匹配的 Controller 方法)
├─► HandlerAdapter(调用 Controller 方法)
│ └─► Controller 方法执行
│ ├── @RequestBody → HttpMessageConverter 反序列化
│ └── 返回值 → HttpMessageConverter 序列化
├─► HandlerExceptionResolver(异常处理)
└─► 响应写回客户端
过滤器(Filter)在 DispatcherServlet 之前;拦截器(Interceptor)在 HandlerAdapter 前后。详见 过滤器与拦截器。
一、Controller 基础
@RestController // @Controller + @ResponseBody
@RequestMapping("/api/v1/orders") // 类级别路径前缀
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
// GET /api/v1/orders/{id}
@GetMapping("/{id}")
public ResponseEntity<OrderDto> getOrder(@PathVariable Long id) {
return ResponseEntity.ok(orderService.findById(id));
}
// GET /api/v1/orders?userId=1&status=PENDING&page=0&size=20
@GetMapping
public Page<OrderDto> listOrders(
@RequestParam Long userId,
@RequestParam(required = false) OrderStatus status,
@PageableDefault(size = 20, sort = "createdAt",
direction = Sort.Direction.DESC) Pageable pageable) {
return orderService.findByUser(userId, status, pageable);
}
// POST /api/v1/orders
@PostMapping
public ResponseEntity<OrderDto> createOrder(
@Valid @RequestBody CreateOrderRequest request) {
OrderDto created = orderService.create(request);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
// PUT /api/v1/orders/{id}
@PutMapping("/{id}")
public OrderDto updateOrder(@PathVariable Long id,
@Valid @RequestBody UpdateOrderRequest request) {
return orderService.update(id, request);
}
// DELETE /api/v1/orders/{id}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteOrder(@PathVariable Long id) {
orderService.delete(id);
}
// PATCH /api/v1/orders/{id}/status
@PatchMapping("/{id}/status")
public OrderDto patchStatus(@PathVariable Long id,
@RequestBody Map<String, String> body) {
OrderStatus status = OrderStatus.valueOf(body.get("status"));
return orderService.updateStatus(id, status);
}
}二、参数绑定
@PathVariable / @RequestParam
// /items/{category}/{id}?color=red&size=L
@GetMapping("/{category}/{id}")
public ItemDto getItem(
@PathVariable String category,
@PathVariable("id") Long itemId,
@RequestParam String color,
@RequestParam(defaultValue = "M") String size,
@RequestParam(required = false) Integer quantity) {
// ...
}@RequestBody(JSON 请求体)
public record CreateOrderRequest(
@NotBlank String productCode,
@Min(1) @Max(99) int quantity,
@NotNull @Valid AddressDto shippingAddress
) { }
@PostMapping
public ResponseEntity<OrderDto> create(@Valid @RequestBody CreateOrderRequest req) {
// req 已经过校验
}参数校验详见 参数校验。
@RequestHeader / @CookieValue
@GetMapping("/profile")
public UserDto getProfile(
@RequestHeader("Authorization") String authHeader,
@RequestHeader(value = "X-Trace-Id", required = false) String traceId,
@CookieValue(value = "session", required = false) String sessionId) {
// ...
}@ModelAttribute(表单/multipart)
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String upload(@ModelAttribute UploadForm form) {
MultipartFile file = form.getFile();
// ...
}文件上传详见 文件上传下载。
三、响应处理
ResponseEntity
// 精确控制状态码、响应头、响应体
return ResponseEntity
.status(HttpStatus.CREATED)
.header("X-Custom-Header", "value")
.contentType(MediaType.APPLICATION_JSON)
.body(dto);
// 无响应体
return ResponseEntity.noContent().build();
// 404
return ResponseEntity.notFound().build();统一响应格式
// 通用响应包装类(详见 [[统一响应格式]])
@Getter
@AllArgsConstructor
public class ApiResponse<T> {
private final boolean success;
private final T data;
private final String message;
public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(true, data, null);
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(false, null, message);
}
}
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
@GetMapping("/{id}")
public ApiResponse<ProductDto> get(@PathVariable Long id) {
return ApiResponse.ok(productService.findById(id));
}
}全局异常处理见 全局异常处理。
四、消息转换
Spring MVC 通过 HttpMessageConverter 处理请求/响应的序列化。默认集成 Jackson:
# application.yml
spring:
jackson:
default-property-inclusion: non_null # null 字段不序列化
serialization:
write-dates-as-timestamps: false # LocalDateTime → ISO-8601 字符串
deserialization:
fail-on-unknown-properties: false # 忽略 JSON 中多余字段
property-naming-strategy: SNAKE_CASE # 驼峰 → 下划线(可选)@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
return JsonMapper.builder()
.addModule(new JavaTimeModule()) // Java 8 时间类型
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();
}
}消息转换详见 消息转换。
五、拦截器
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod method = (HandlerMethod) handler;
// 检查是否有 @SkipAuth 注解
if (method.hasMethodAnnotation(SkipAuth.class)) {
return true;
}
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.sendError(HttpStatus.UNAUTHORIZED.value(), "未登录");
return false; // 拦截请求
}
return true;
}
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**", "/actuator/**");
}
}六、静态资源与视图
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 自定义静态资源路径
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(7, TimeUnit.DAYS));
// 上传文件目录
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:/var/uploads/");
}
// CORS 配置(另有 @CrossOrigin 注解和 CorsFilter 方案)
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://app.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}七、内容协商
同一接口根据请求的 Accept 头返回不同格式:
// 引入 Jackson XML 支持
// <dependency>com.fasterxml.jackson.dataformat:jackson-dataformat-xml</dependency>
@GetMapping(value = "/report",
produces = {MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE})
public ReportDto getReport() {
return reportService.generate();
}GET /report
Accept: application/xml八、WebMvcConfigurer 常用扩展
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 注册自定义参数解析器
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CurrentUserArgumentResolver());
}
// 注册自定义返回值处理器
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
// ...
}
// 注册消息转换器(追加,不替换默认)
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new ProtobufHttpMessageConverter());
}
// 格式化器(日期字符串 → LocalDate 等)
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToLocalDateConverter());
}
}自定义注解参数解析器
// 从 ThreadLocal / JWT 中解析当前用户,注入到 Controller 方法参数
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser { }
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
return SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal();
}
}
// 使用
@GetMapping("/me")
public UserDto getMe(@CurrentUser UserDetails user) {
return userService.findByUsername(user.getUsername());
}九、异步 Controller
@RestController
@RequestMapping("/api/reports")
public class ReportController {
private final ReportService reportService;
// 返回 Callable:在 Spring MVC 线程池中执行,释放 Tomcat 线程
@GetMapping("/callable")
public Callable<ReportDto> generateReport() {
return () -> reportService.generateHeavyReport();
}
// 返回 CompletableFuture
@GetMapping("/async")
public CompletableFuture<ReportDto> generateAsync() {
return reportService.generateAsync();
}
// 返回 DeferredResult:手动控制完成时机(适合回调通知)
@GetMapping("/deferred")
public DeferredResult<ReportDto> generateDeferred() {
DeferredResult<ReportDto> result = new DeferredResult<>(30_000L);
reportService.generateWithCallback(
dto -> result.setResult(dto),
ex -> result.setErrorResult(ex)
);
return result;
}
}异步与线程池配置详见 异步与线程池。
常见配置速查
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher # Spring Boot 3 默认 PathPatternParser
server:
servlet:
context-path: /api # 全局路径前缀
tomcat:
threads:
max: 200 # Tomcat 最大线程数
min-spare: 10
connection-timeout: 20s
max-swallow-size: 10MB # 最大请求体大小(上传限制另见 multipart)
compression:
enabled: true # Gzip 压缩
mime-types: application/json,text/html
min-response-size: 2KB相关链接
- 过滤器与拦截器 — Filter vs HandlerInterceptor 的选择与执行顺序
- 全局异常处理 —
@RestControllerAdvice统一异常响应 - 参数校验 —
@Valid/@Validated校验请求参数 - 统一响应格式 — 统一包装 API 响应体
- 消息转换 — Jackson 配置与自定义序列化
- 跨域处理 — CORS 配置方式
- 静态资源 — 静态文件缓存策略
- 异步与线程池 — 异步 Controller 与线程池配置
- 文件上传下载 — multipart 文件处理
- 安全 — Spring Security 与 MVC 的集成
- AOP — Controller 层切面(日志、性能监控)