1. 项目概述
Spring MVC作为Java Web开发的核心框架,其请求处理流程一直是开发者必须掌握的重点知识。这个标题直指Spring MVC最核心的机制 - 请求的全生命周期处理。在实际开发中,很多性能问题、功能异常都源于对请求处理流程理解不够深入。
我曾在多个电商项目中处理过因请求流程理解偏差导致的典型问题:比如拦截器执行顺序混乱造成的参数校验失效、视图解析器配置不当导致的模板渲染性能瓶颈等。通过源码级别的分析,不仅能快速定位问题,更能从架构层面优化设计。
2. 核心流程拆解
2.1 请求入口与分发机制
Spring MVC的请求入口是DispatcherServlet,这是整个流程的总调度中心。当请求到达时:
- HttpServletRequest首先被容器(如Tomcat)接收
- 容器将请求交给配置的DispatcherServlet处理
- DispatcherServlet的doService()方法开始处理流程
关键点在于HandlerMapping的匹配过程。Spring MVC内置了多种HandlerMapping实现:
- RequestMappingHandlerMapping(用于@RequestMapping)
- BeanNameUrlHandlerMapping
- SimpleUrlHandlerMapping
它们的getHandler()方法会根据请求URL找到对应的Controller方法。这里常见的坑是:
注意:当存在多个匹配的HandlerMapping时,会按照order属性排序,order值越小优先级越高。我曾遇到过自定义HandlerMapping因未设置order属性导致匹配失效的问题。
2.2 拦截器执行链
HandlerExecutionChain是另一个关键组件,它包含:
- 目标Handler(Controller方法)
- 拦截器列表(HandlerInterceptor)
执行顺序为:
preHandle() → Controller方法 → postHandle() → afterCompletion()
实测中发现一个典型问题:如果preHandle()返回false,不仅会跳过Controller方法,postHandle()也不会执行,但已通过的preHandle()对应的afterCompletion()仍会执行。这个特性在权限校验时需要特别注意。
2.3 参数解析与绑定
HandlerMethodArgumentResolver是参数解析的核心接口,Spring MVC内置了30+种实现:
- RequestParamMethodArgumentResolver(处理@RequestParam)
- RequestResponseBodyMethodProcessor(处理@RequestBody)
- PathVariableMethodArgumentResolver(处理@PathVariable)
我曾遇到一个复杂对象绑定性能问题:当使用@ModelAttribute绑定包含大量字段的对象时,默认的DataBinder会反射检查所有字段属性。解决方案是使用@InitBinder指定allowedFields:
java复制@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setAllowedFields("field1", "field2");
}
2.4 返回值处理
HandlerMethodReturnValueHandler处理Controller方法的返回值,常见实现:
- ModelAndViewMethodReturnValueHandler
- RequestResponseBodyMethodProcessor
- ViewNameMethodReturnValueHandler
一个实用技巧:当需要统一处理返回值时,可以实现ResponseBodyAdvice接口。比如下面的签名验案例子:
java复制@ControllerAdvice
public class SignAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(...) {
return true;
}
@Override
public Object beforeBodyWrite(...) {
// 添加响应签名
response.addHeader("X-Sign", generateSign(body));
return body;
}
}
3. 核心组件源码解析
3.1 DispatcherServlet类结构
DispatcherServlet的继承体系:
HttpServlet ← FrameworkServlet ← DispatcherServlet
关键方法:
- doService(): 入口方法,设置上下文
- doDispatch(): 核心分发逻辑
- processDispatchResult(): 处理结果
在doDispatch()中有个细节:对于文件上传请求,会先调用MultipartResolver的resolveMultipart()方法将请求转换为MultipartHttpServletRequest。如果上传大文件时内存溢出,需要配置:
properties复制spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
3.2 HandlerAdapter执行流程
RequestMappingHandlerAdapter的工作流程:
- 调用拦截器preHandle()
- 解析参数(invokeHandlerMethod)
- 执行Controller方法
- 处理返回值
- 调用拦截器postHandle()
其中参数解析的invokeArgumentResolvers()方法值得关注。它使用ResolverComposite来管理所有参数解析器,通过supportsParameter()方法判断是否支持当前参数。
3.3 视图渲染过程
ViewResolver将逻辑视图名解析为实际View对象的过程:
- 遍历所有ViewResolver
- 调用resolveViewName()
- 返回第一个非null的View
性能优化点:InternalResourceViewResolver在解析JSP时会检查文件是否存在,生产环境可以通过设置以下属性禁用检查:
java复制viewResolver.setCheckExistence(false);
4. 实战中的典型问题
4.1 跨域配置失效问题
当同时使用@CrossOrigin和自定义拦截器时,可能会遇到跨域配置失效。这是因为:
- CORS处理发生在拦截器之前
- 拦截器修改响应头可能导致CORS头丢失
解决方案:
- 使用Filter代替拦截器
- 或者在拦截器中手动添加CORS头
4.2 文件上传大小限制
除了配置spring.servlet.multipart属性外,Tomcat本身也有限制:
properties复制# Tomcat 8.5+
server.tomcat.max-swallow-size=10MB
如果上传文件超过这个大小,连接会被直接关闭,不会返回任何错误信息。
4.3 异步请求处理
使用@Async时需要注意:
- 必须启用@EnableAsync
- 返回值类型应为Future/CompletableFuture等
- 拦截器的afterCompletion()会在主线程结束时立即调用,而非异步任务完成时
对于长时间异步任务,建议使用DeferredResult或Callable:
java复制@GetMapping("/async")
public DeferredResult<String> async() {
DeferredResult<String> result = new DeferredResult<>();
// 异步处理
executor.execute(() -> {
Thread.sleep(1000);
result.setResult("Done");
});
return result;
}
5. 性能调优实践
5.1 路由匹配优化
RequestMappingHandlerMapping默认使用AntPathMatcher进行路径匹配。在路由数量多(100+)时,可以:
- 切换到PathPatternParser(Spring 5.3+)
- 对常用路径使用直接匹配(如"/api/v1/users"优于"/api/**")
测试数据显示,PathPatternParser比AntPathMatcher快约30%。
5.2 参数解析缓存
Spring MVC默认会缓存HandlerMethod的解析结果。但在以下情况会重新解析:
- 热部署场景
- 动态添加Controller
可以通过自定义HandlerMethodMapping来优化:
java复制public class CachingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected boolean isHandler(Class<?> beanType) {
// 更精确的控制哪些类需要被映射
}
}
5.3 视图解析优化
对于REST API项目,可以完全禁用视图解析:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// 无操作
}
}
这可以减少不必要的视图查找开销。
6. 扩展机制深度应用
6.1 自定义参数解析器
实现HandlerMethodArgumentResolver接口可以扩展参数解析能力。比如解析JWT token:
java复制public class JwtArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JwtClaim.class);
}
@Override
public Object resolveArgument(...) {
String token = request.getHeader("Authorization");
return JwtParser.parse(token);
}
}
注册解析器:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new JwtArgumentResolver());
}
}
6.2 异常处理进阶
除了@ExceptionHandler,还可以实现HandlerExceptionResolver:
java复制public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(...) {
if (ex instanceof BusinessException) {
return new ModelAndView(new MappingJackson2JsonView(),
Map.of("error", ex.getMessage()));
}
return null; // 交给其他解析器
}
}
这种方式的优势是可以处理拦截器中抛出的异常。
6.3 动态Controller注册
通过编程方式注册Controller:
java复制@RestController
@RequestMapping("/dynamic")
public class DynamicController {
private final RequestMappingHandlerMapping handlerMapping;
public DynamicController(RequestMappingHandlerMapping handlerMapping) {
this.handlerMapping = handlerMapping;
}
@PostMapping("/register")
public String register(String path) throws Exception {
Method method = getClass().getMethod("handler");
RequestMappingInfo mapping = RequestMappingInfo
.paths(path)
.methods(RequestMethod.GET)
.build();
handlerMapping.registerMapping(mapping, this, method);
return "Registered: " + path;
}
public String handler() {
return "Dynamic response";
}
}
这个技巧在实现动态路由时非常有用。
7. 监控与诊断
7.1 请求日志记录
实现Filter记录请求信息:
java复制public class RequestLogFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
long start = System.currentTimeMillis();
chain.doFilter(request, response);
long duration = System.currentTimeMillis() - start;
HttpServletRequest req = (HttpServletRequest) request;
log.info("{} {} - {}ms", req.getMethod(), req.getRequestURI(), duration);
}
}
注意:对于文件上传请求,需要先调用MultipartResolver。
7.2 性能指标收集
使用Micrometer收集MVC指标:
java复制@Bean
public FilterRegistrationBean<MetricsFilter> metricsFilter() {
FilterRegistrationBean<MetricsFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new MetricsFilter(registry));
registration.addUrlPatterns("/*");
return registration;
}
可以监控:
- 请求计数
- 处理时间
- 异常计数
7.3 热图分析
通过HandlerMapping获取所有路由信息:
java复制@Autowired
private RequestMappingHandlerMapping handlerMapping;
@GetMapping("/routes")
public Map<String, String> listRoutes() {
return handlerMapping.getHandlerMethods().entrySet().stream()
.collect(Collectors.toMap(
e -> e.getKey().toString(),
e -> e.getValue().getMethod().toString()
));
}
这个端点可以帮助分析系统中实际存在的API接口。