Spring MVC核心原理与RESTful最佳实践详解
一、DispatcherServlet:请求处理中枢
1.1 核心职责与架构
DispatcherServlet是Spring MVC的前端控制器(Front Controller) ,所有HTTP请求的统一入口。它继承自HttpServlet,遵循Servlet规范,是整个MVC架构的调度核心。
关键特性:
- 单例模式:每个应用一个实例,线程安全
- 职责分离:仅负责调度,不处理具体业务
- 可扩展性:通过策略模式支持多种Handler、ViewResolver等组件
1.2 请求处理完整流程
java
// DispatcherServlet核心方法伪代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
try {
// 1. 文件上传请求预处理
processedRequest = checkMultipart(request);
// 2. 获取HandlerExecutionChain(Handler+拦截器链)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 3. 获取HandlerAdapter(适配不同Controller类型)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 4. 执行拦截器preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return; // 拦截器返回false,终止请求
}
// 5. 调用Handler处理请求(核心)
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 6. 执行拦截器postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
// 异常记录
dispatchException = ex;
}
// 7. 结果处理(视图渲染或异常处理)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
// 8. 执行拦截器afterCompletion(无论成功失败)
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
流程图:
HTTP Request
│
▼
DispatcherServlet.doDispatch()
│
├─▶ 1. MultipartResolver.resolveMultipart() [文件上传]
│
├─▶ 2. HandlerMapping.getHandler() [路由查找]
│ └─▶ HandlerExecutionChain
│ ├─ HandlerMethod
│ └─ HandlerInterceptor[] (preHandle/postHandle/afterCompletion)
│
├─▶ 3. HandlerAdapter.supports() [适配器选择]
│
├─▶ 4. HandlerInterceptor.preHandle() [前置拦截]
│
├─▶ 5. HandlerAdapter.handle() [执行业务逻辑]
│ ├─ HandlerMethodArgumentResolver [参数解析]
│ ├─ 调用@Controller方法 [业务处理]
│ └─ HandlerMethodReturnValueHandler [返回值处理]
│
├─▶ 6. HandlerInterceptor.postHandle() [后置拦截]
│
├─▶ 7. ViewResolver.resolveViewName() [视图解析]
│ └─ View.render() [视图渲染]
│
└─▶ 8. HandlerInterceptor.afterCompletion() [最终清理]
二、HandlerMapping:智能路由决策
2.1 核心实现类
HandlerMapping将请求映射到HandlerExecutionChain,Spring提供多种实现:
| 实现类 | 用途 | 匹配规则 | 优先级 |
|---|---|---|---|
| RequestMappingHandlerMapping | 注解驱动 | @RequestMapping等注解 | 最高 |
| SimpleUrlHandlerMapping | 静态URL映射 | URL路径 | 中 |
| BeanNameUrlHandlerMapping | Bean名称映射 | Bean名称以/开头 | 低 |
| RouterFunctionMapping | 函数式路由 | RouterFunction | WebFlux |
2.2 RequestMappingHandlerMapping工作原理
初始化阶段(启动时):
java
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping {
@Override
public void afterPropertiesSet() {
// 扫描所有@Controller/@RestController Bean
String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
Class<?> beanType = obtainApplicationContext().getType(beanName);
if (beanType != null && isHandler(beanType)) {
// 探测所有@RequestMapping方法
detectHandlerMethods(beanName);
}
}
}
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = handler.getClass();
final Class<?> userType = ClassUtils.getUserClass(handlerType);
// 反射获取所有方法
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
// 注册到MappingRegistry
methods.forEach((method, mapping) -> {
registerHandlerMethod(handler, method, mapping);
});
}
}
请求阶段(运行时):
java
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request);
// 从MappingRegistry中精确匹配
return lookupHandlerMethod(lookupPath, request);
}
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 查找所有匹配的RequestMappingInfo
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 无直接匹配,尝试模糊匹配
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
// 选择最佳匹配(根据模式精确度、参数数量等)
Match bestMatch = matches.get(0);
return bestMatch.handlerMethod;
}
return null;
}
2.3 自定义HandlerMapping示例
需求:为API添加版本前缀
java
// 1. 定义版本注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
String value();
}
// 2. 自定义HandlerMapping
@Component
public class ApiVersionHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
if (info == null) return null;
// 检查类或方法上的版本注解
ApiVersion typeVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
ApiVersion methodVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);
String version = (methodVersion != null) ? methodVersion.value() :
(typeVersion != null) ? typeVersion.value() : null;
if (version != null) {
// 构建新路径条件:/api/v{version}/path
PatternsRequestCondition oldCondition = info.getPathPatternsCondition();
PatternsRequestCondition versionedCondition = new PatternsRequestCondition(
oldCondition.getPatternValues().stream()
.map(pattern -> "/api/v" + version + pattern)
.toArray(String[]::new)
);
return RequestMappingInfo.paths(versionedCondition.getPatternValues())
.methods(info.getMethodsCondition().getMethods())
.params(info.getParamsCondition().getExpressions())
.headers(info.getHeadersCondition().getHeaders())
.consumes(info.getConsumesCondition().getConsumableMediaTypes())
.produces(info.getProducesCondition().getProducibleMediaTypes())
.build();
}
return info;
}
}
// 3. 使用示例
@RestController
@ApiVersion("2")
@RequestMapping("/users")
public class UserControllerV2 {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) { ... }
}
// 实际路径:/api/v2/users/{id}
三、参数解析:从请求到方法入参
3.1 HandlerMethodArgumentResolver架构
Spring MVC通过策略模式支持30+种参数类型,核心流程:
java
public class RequestMappingHandlerAdapter {
private List<HandlerMethodArgumentResolver> argumentResolvers;
protected Object[] getMethodArgumentValues(NativeWebRequest request,
ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
// 查找支持的解析器
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
// 解析参数
args[i] = resolver.resolveArgument(parameter, mavContainer, request, dataBinderFactory);
}
return args;
}
}
3.2 核心解析器详解
| 解析器 | 支持注解/类型 | 数据转换机制 |
|---|---|---|
| RequestParamMethodArgumentResolver | @RequestParam |
Converter/PropertyEditor |
| PathVariableMethodArgumentResolver | @PathVariable |
字符串强制转换 |
| RequestBodyArgumentResolver | @RequestBody |
HttpMessageConverter |
| ModelAttributeMethodProcessor | @ModelAttribute |
WebDataBinder |
| ServletRequestMethodArgumentResolver | HttpServletRequest |
直接传递 |
3.3 参数解析实战示例
示例1:复杂查询参数封装
java
@Data
public class UserQuery {
private String username;
private Integer minAge;
private Integer maxAge;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate createdSince;
}
@GetMapping("/search")
public Page<User> searchUsers(UserQuery query, Pageable pageable) {
// URL: /users/search?username=jack&minAge=18&page=0&size=20
return userService.search(query, pageable);
}
示例2:自定义解析器(当前登录用户)
java
// 1. 定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {}
// 2. 实现解析器
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentUser.class)
&& parameter.getParameterType().equals(User.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null) {
throw new UnauthorizedException("用户未登录");
}
return userService.getByUsername(auth.getName());
}
}
// 3. 注册解析器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CurrentUserArgumentResolver());
}
}
// 4. 使用
@GetMapping("/profile")
public UserProfile getProfile(@CurrentUser User user) {
return userService.getProfile(user.getId());
}
四、响应处理:从返回值到HTTP响应
4.1 HandlerMethodReturnValueHandler体系
Spring MVC通过返回值处理器将方法结果转换为HTTP响应,支持多种返回类型。
| 处理器 | 支持类型 | 处理方式 |
|---|---|---|
| RequestResponseBodyMethodProcessor | @ResponseBody |
JSON/XML序列化 |
| ViewNameMethodReturnValueHandler | String(视图名) |
视图渲染 |
| ModelAndViewMethodReturnValueHandler | ModelAndView |
模型+视图 |
| HttpEntityMethodProcessor | ResponseEntity |
完整HTTP响应控制 |
| CallableMethodReturnValueHandler | Callable |
异步处理 |
4.2 @ResponseBody与HttpMessageConverter
java
@GetMapping("/users/{id}")
@ResponseBody // 标识返回值直接写入HTTP body
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
转换流程:
java
// 1. RequestResponseBodyMethodProcessor处理
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
// 标记请求已处理
mavContainer.setRequestHandled(true);
// 2. 选择合适的HttpMessageConverter
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
writeWithMessageConverters(returnValue, returnType, outputMessage);
}
// 3. 使用Jackson序列化(MappingJackson2HttpMessageConverter)
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException {
ObjectMapper objectMapper = getObjectMapper();
objectMapper.writeValue(outputMessage.getBody(), object);
}
4.3 ResponseEntity:完全响应控制
java
// 场景1:201 Created + Location头
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserDTO dto) {
User created = userService.create(dto);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
// 状态码: 201
// Location: /api/v1/users/123
}
// 场景2:204 No Content
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
// 状态码: 204,body为空
}
// 场景3:条件响应
@GetMapping("/orders/{id}")
public ResponseEntity<Order> getOrder(@PathVariable Long id,
@RequestHeader(value = "If-None-Match", required = false) String etag) {
Order order = orderService.findById(id);
String currentEtag = generateEtag(order);
if (currentEtag.equals(etag)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build(); // 304
}
return ResponseEntity.ok()
.eTag(currentEtag)
.cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
.body(order);
}
4.4 文件下载与流式响应
java
// 文件下载
@GetMapping("/reports/{id}/download")
public ResponseEntity<Resource> downloadReport(@PathVariable Long id) {
Report report = reportService.getReport(id);
Resource resource = new ByteArrayResource(report.getContent());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + report.getFilename() + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(report.getSize())
.body(resource);
}
// SSE(Server-Sent Events)推送
@GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamEvents() {
SseEmitter emitter = new SseEmitter();
// 异步发送事件
CompletableFuture.runAsync(() -> {
try {
for (int i = 0; i < 10; i++) {
emitter.send(SseEmitter.event()
.id(String.valueOf(i))
.name("message")
.data("Event " + i));
Thread.sleep(1000);
}
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter; // 保持连接,持续推送
}
五、RESTful API最佳实践
5.1 URL设计黄金法则
1. 资源命名规范:
- 使用名词复数表示集合
- 避免动词(用HTTP方法表达动作)
- 层级关系用
/,过滤条件用?
java
// ✅ 良好设计
GET /api/v1/users // 获取用户列表
GET /api/v1/users?role=admin // 过滤
GET /api/v1/users/{id} // 获取单个用户
POST /api/v1/users // 创建用户
PUT /api/v1/users/{id} // 全量更新
PATCH /api/v1/users/{id} // 部分更新
DELETE /api/v1/users/{id} // 删除用户
GET /api/v1/users/{id}/orders // 子资源(用户的订单)
// ❌ 反模式
GET /api/v1/getUser // 动词
GET /api/v1/users/getInfo // 冗余动词
POST /api/v1/users/create // 动词,应直接用POST
GET /api/v1/deleteUser // 动词,应使用DELETE
2. 版本管理:
java
// 方式1:URL版本化(推荐,简单明确)
@RequestMapping("/api/v1/users") // 稳定版本
@RequestMapping("/api/v2/users") // 新功能版本
// 方式2:Accept头版本化(更RESTful)
@GetMapping(value = "/users",
produces = "application/vnd.company.v1+json")
public ResponseEntity<List<User>> getUsersV1() { ... }
@GetMapping(value = "/users",
produces = "application/vnd.company.v2+json")
public ResponseEntity<List<User>> getUsersV2() { ... }
5.2 HTTP状态码使用指南
| HTTP方法 | 成功 | 创建 | 无内容 | 错误 | 认证失败 | 无权限 | 未找到 |
|---|---|---|---|---|---|---|---|
| GET | 200 | - | - | 400 | 401 | 403 | 404 |
| POST | - | 201 | 204 | 400 | 401 | 403 | 404 |
| PUT | 200 | - | 204 | 400 | 401 | 403 | 404 |
| PATCH | 200 | - | 204 | 400 | 401 | 403 | 404 |
| DELETE | 204 | - | - | 400 | 401 | 403 | 404 |
Controller实现:
java
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
@GetMapping("/{id}")
public ResponseEntity<Order> getOrder(@PathVariable Long id) {
return orderService.findById(id)
.map(ResponseEntity::ok) // 200
.orElse(ResponseEntity.notFound().build()); // 404
}
@PostMapping
public ResponseEntity<Order> createOrder(@Valid @RequestBody OrderDTO dto) {
Order created = orderService.create(dto);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created); // 201 + Location头
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
orderService.delete(id);
return ResponseEntity.noContent().build(); // 204
}
}
5.3 统一错误响应格式
java
// 错误响应DTO
@Data
@AllArgsConstructor
public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
}
// 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex, HttpServletRequest request) {
log.error("系统异常", ex);
ErrorResponse error = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Internal Server Error",
"系统繁忙,请稍后重试",
request.getRequestURI()
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex, HttpServletRequest request) {
String message = ex.getBindingResult().getFieldErrors().stream()
.map(err -> err.getField() + ": " + err.getDefaultMessage())
.collect(Collectors.joining(", "));
ErrorResponse error = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.BAD_REQUEST.value(),
"Validation Failed",
message,
request.getRequestURI()
);
return ResponseEntity.badRequest().body(error);
}
}
响应示例:
json
// 400 Bad Request
{
"timestamp": "2024-01-07T10:30:00",
"status": 400,
"error": "Validation Failed",
"message": "age: 必须大于0, email: 格式不正确",
"path": "/api/v1/users"
}
5.4 HATEOAS:超媒体驱动API
通过_links提供资源导航关系,实现API自描述。
java
// 添加spring-boot-starter-hateoas依赖
@RestController
@RequestMapping("/api/v1/orders")
public class OrderHateoasController {
@GetMapping("/{id}")
public EntityModel<Order> getOrder(@PathVariable Long id) {
Order order = orderService.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id));
// 包装资源+链接
return EntityModel.of(order,
linkTo(methodOn(OrderController.class).getOrder(id)).withSelfRel(),
linkTo(methodOn(OrderController.class).cancelOrder(id)).withRel("cancel"),
linkTo(methodOn(OrderController.class).getOrderItems(id)).withRel("items")
);
}
}
// 响应示例
{
"id": 123,
"status": "PENDING",
"amount": 99.99,
"_links": {
"self": {
"href": "https://api.example.com/api/v1/orders/123"
},
"cancel": {
"href": "https://api.example.com/api/v1/orders/123/cancel"
},
"items": {
"href": "https://api.example.com/api/v1/orders/123/items"
}
}
}
5.5 生产级最佳实践
1. 请求追踪与日志
java
@Component
public class TraceIdFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String traceId = request.getHeader("X-Trace-Id");
if (traceId == null) {
traceId = UUID.randomUUID().toString();
}
MDC.put("traceId", traceId);
response.setHeader("X-Trace-Id", traceId);
try {
filterChain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
2. API限流
java
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String clientId = getClientId(request);
Bucket bucket = buckets.computeIfAbsent(clientId, this::createBucket);
if (bucket.tryConsume(1)) {
filterChain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Rate limit exceeded");
}
}
private Bucket createBucket(String key) {
Bandwidth bandwidth = Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1)));
return Bucket.builder().addLimit(bandwidth).build();
}
}
3. 接口文档(OpenAPI)
java
@Configuration
@SecurityScheme(
name = "bearerAuth",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
public class OpenAPIConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("订单服务API")
.version("v1")
.description("提供订单创建、查询、修改等功能")
.contact(new Contact().name("技术部").email("tech@example.com")))
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"));
}
}
// Controller注解
@Operation(summary = "创建订单", description = "创建新的订单记录")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "订单创建成功"),
@ApiResponse(responseCode = "400", description = "请求参数无效"),
@ApiResponse(responseCode = "401", description = "未认证")
})
@PostMapping
public ResponseEntity<Order> createOrder(@Valid @RequestBody OrderDTO dto) {
return ResponseEntity.created(...).body(created);
}
六、总结
Spring MVC通过DispatcherServlet 的统筹调度,实现了请求从接收到响应的全生命周期管理。掌握其内部机制(HandlerMapping、参数解析、返回值处理)能帮助开发者更好地调试和扩展。遵循RESTful最佳实践(URL规范、状态码、HATEOAS、统一错误处理)可构建出高可维护性、自描述、面向演进的生产级API。
核心口诀:DispatcherServlet管调度,HandlerMapping做路由,ArgumentResolver解析参,ReturnValueHandler管响应;URL用复数名词,动词交给HTTP方法,状态码精确表达,错误响应统一格式。