Spring Framework 7.0.9 深度剖析-17-WebMVC 请求处理链路
17.1 本章导读
一个 HTTP 请求从被 Servlet 容器接收到最终响应体写回,在 Spring WebMVC 中要穿过多少层?本章的目标,就是把这条链路在 Spring Framework 7.0.9 源码里逐类、逐方法、逐字段地走一遍:请求如何被 HttpServletBean.init 装配、如何经由 FrameworkServlet.processRequest 把请求上下文绑定到当前线程、DispatcherServlet.doDispatch 如何完成「multipart 判定 → 找 Handler → 跑拦截器 preHandle → 找 HandlerAdapter → 反射调用 Controller 方法 → 解析参数 → 处理返回值 → 视图渲染或消息体写回 → 异常解析 → 拦截器 postHandle/afterCompletion」这一整套流水线。
涉及的核心模块与类(均以本章仓库 spring-framework-7.0.9 为准):
spring-webmvc模块:org.springframework.web.servlet包下的HttpServletBean、FrameworkServlet、DispatcherServlet、HandlerExecutionChain、HandlerMapping、HandlerAdapter、HandlerInterceptor、ModelAndView、View、ViewResolver、HandlerExceptionResolver;以及handler、mvc.method.annotation、view、mvc.support等子包。spring-web模块:org.springframework.http.converter包下的消息转换器体系(AbstractHttpMessageConverter等)、org.springframework.web.method.support包下的InvocableHandlerMethod与两个 Composite 解析器、org.springframework.web.context.request.async包下的WebAsyncManager,以及org.springframework.web.multipart.support.StandardServletMultipartResolver。
前置知识:读者应了解 Servlet 规范中 HttpServlet 的 service/doGet/doPost 分派机制、Spring IoC 容器的基本使用、以及 @Controller/@RequestMapping/@ResponseBody 的常规用法。读完本章,你应当能够回答以下问题:
- 为什么
DispatcherServlet自己不直接调用 Controller,而要经过HandlerMapping与HandlerAdapter两层间接?这两层各自解决了什么变化点? @RequestParam、@PathVariable、@RequestBody这些注解是在哪个类、哪一行被识别并取值的?为什么一个未被任何解析器识别的简单类型参数会被当作请求参数?@ResponseBody返回一个对象后,JSON 是在哪一步、由哪个类、按照什么规则选中MappingJackson2HttpMessageConverter写出去的?Accept 头与 produces 如何影响最终 Content-Type?- 拦截器的
preHandle返回 false 时,为什么只有「已成功 preHandle 过的那几个」拦截器的afterCompletion会被回调?这个状态记在哪? - 404 与 405 在源码中分别在哪一行被抛出、被哪个
HandlerExceptionResolver接住?
本章不假设读者读过本系列其他章节,所有机制都自包含地讲透。
17.2 核心概念:链路中的关键抽象
在钻进源码之前,先把链路上反复出现的几个抽象钉死。它们不是名词表,每个概念都对应一个真实接口及其在链路中的职责。
17.2.1 HandlerMapping:请求到处理器的路由器
HandlerMapping(spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerMapping.java)的核心契约是:
public interface HandlerMapping {
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
它负责回答「这个 URL 应该交给哪个对象处理」。注意返回值不是裸 handler,而是 HandlerExecutionChain——handler 本体加上一组匹配到的拦截器。DispatcherServlet 内部维护一个 List<HandlerMapping> handlerMappings,按 AnnotationAwareOrderComparator 排序后顺序遍历,谁先返回非 null 的 chain 就用谁。7.0.9 中内置的三大实现:BeanNameUrlHandlerMapping(以 bean 名为 URL)、RequestMappingHandlerMapping(扫描 @Controller 的 @RequestMapping,是绝大多数应用的主力)、RouterFunctionMapping(函数式路由)。默认清单见 spring-webmvc/src/main/resources/org/springframework/web/servlet/DispatcherServlet.properties。
17.2.2 HandlerAdapter:调用处理器的统一端口
为什么有了 HandlerMapping 还需要 HandlerAdapter?因为 handler 的形态千差万别:它可能是一个实现了 Controller 接口的老风格对象、可能是一个 HttpRequestHandler、也可能是一个被包装成 HandlerMethod 的注解式 Controller 方法。直接调用要求每种 handler 都实现同一个接口会束缚扩展性。HandlerAdapter(spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerAdapter.java)用「适配」模式解耦:
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
DispatcherServlet 拿到 chain 里的 handler 后,遍历 handlerAdapters 列表,找到第一个 supports(handler) 返回 true 的适配器,由它去完成真正的反射调用。7.0.9 默认内置四个适配器:HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、RequestMappingHandlerAdapter、HandlerFunctionAdapter。
17.2.3 HandlerMethod 与 ServletInvocableHandlerMethod
注解式 Controller 的一个方法(比如 UserController#getUser(Long id))被 RequestMappingHandlerMapping 封装成 HandlerMethod(spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java),它持有 bean 实例、Method 对象、以及把方法参数包装成 MethodParameter 的能力。进入适配器后,它再被包装成 InvocableHandlerMethod(spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java)——负责「解析参数 → 反射调用」;在 Servlet 环境下进一步包装为 ServletInvocableHandlerMethod(spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java)——额外负责「调用后用返回值处理器处理返回值、写响应状态码」。
17.2.4 HandlerExecutionChain:处理器 + 拦截器的执行链
如前所述,它是 handler 本体和 List<HandlerInterceptor> 的组合,内部用一个 int interceptorIndex 字段记录「当前执行到第几个拦截器」。preHandle 顺序执行、postHandle 逆序执行、afterCompletion 从 interceptorIndex 逆序回退——这个字段是理解拦截器短路行为的钥匙,本章第 17.6 节会逐行拆。
17.2.5 ModelAndView:视图与模型的载体
ModelAndView(spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java)内部持有两部分:Object view(可能是视图名字符串,也可能是 View 实例)和 ModelMap model(一个 Map<String,Object>)。HandlerAdapter 调用 handler 后返回它,DispatcherServlet 据此决定是「按名字解析视图再渲染」还是「直接用给定 View 渲染」。对于 @ResponseBody 场景,返回值处理器会把 mavContainer.setRequestHandled(true),于是 doDispatch 后续根本不渲染视图。
17.2.6 ViewResolver 与 View
ViewResolver(spring-webmvc/src/main/java/org/springframework/web/servlet/ViewResolver.java)契约只有一个方法:View resolveViewName(String viewName, Locale locale)。DispatcherServlet 同样维护一个排好序的 List<ViewResolver> viewResolvers,顺序尝试,谁先返回非 null 的 View 就用谁。View 接口(View.java)的核心是 void render(Map<String,?> model, HttpServletRequest request, HttpServletResponse response)。视图名解析与消息体写回是两条互斥的终点。
17.2.7 HandlerExceptionResolver:异常到错误视图的转换器
HandlerExceptionResolver(spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExceptionResolver.java)契约:ModelAndView resolveException(HttpServletRequest, HttpServletResponse, Object handler, Exception ex)。当 doDispatch 内层 try 捕获到异常时,把异常交给排好序的 resolver 链,第一个返回非 null ModelAndView 的 resolver 胜出。7.0.9 默认三链:ExceptionHandlerExceptionResolver(处理 @ExceptionHandler 与 @ControllerAdvice)、ResponseStatusExceptionResolver(处理 @ResponseStatus 注解与 ResponseStatusException)、DefaultHandlerExceptionResolver(把标准 Servlet 异常映射到对应 HTTP 状态码)。
17.2.8 MultipartResolver、WebAsyncManager、FlashMapManager
这三个是 DispatcherServlet 初始化时装配的「边缘策略」:MultipartResolver(spring-web/src/main/java/org/springframework/web/multipart/MultipartResolver.java)在请求进入 doDispatch 第一步把文件上传请求包成 MultipartHttpServletRequest;WebAsyncManager(spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java)管理 Callable/DeferredResult 的异步挂起与恢复;FlashMapManager 负责重定向间的临时参数传递。它们的共同特点是:在 doDispatch 主流程里只占很薄的一层,但每一层都对应一个明确的扩展点。
把这些抽象摆在一起,整条链路的骨架就是:请求 → FrameworkServlet 绑定上下文 → DispatcherServlet 选择 HandlerMapping 得到 chain → 拦截器 preHandle → HandlerAdapter 反射调用方法(内部完成参数解析与返回值处理)→ 成功则渲染视图/写消息体,失败则异常 resolver 链 → 拦截器 postHandle/afterCompletion → FrameworkServlet 清理线程上下文。后续每一节都是对这个骨架的源码级展开。
17.3 源码剖析(一):Servlet 适配与继承链
WebMVC 的请求入口不是 DispatcherServlet 自己,而是三层 Servlet 子类构成的继承链:HttpServlet → HttpServletBean → FrameworkServlet → DispatcherServlet。理解这条链,等于理解了「Spring 为什么能把一个标准 Servlet 改造成 IoC 容器感知的分发器」。
17.3.1 HttpServletBean:把 init-param 变成 bean 属性
HttpServletBean(spring-webmvc/src/main/java/org/springframework/web/servlet/HttpServletBean.java)继承 HttpServlet,实现 EnvironmentCapable, EnvironmentAware。它的全部价值在 init() 方法上,注意它被声明为 final——子类不能重写初始化入口:
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
逐段解读:
ServletConfigPropertyValues(本类的私有静态内部类,第 209-242 行)把 Servlet 容器传来的ServletConfig里所有init-param枚举出来,封装成MutablePropertyValues。它还持有一个requiredProperties集合——子类可以在构造期调用addRequiredProperty(String)声明某个参数必填,构造函数末尾若missingProps非空,直接抛ServletException并列出缺哪些属性。BeanWrapper:这是 Spring beans 包的属性访问抽象。PropertyAccessorFactory.forBeanPropertyAccess(this)把 DispatcherServlet 自己当成一个 bean 来包装。于是 web.xml 里写的<init-param><param-name>contextConfigLocation</param-name>...会被自动类型转换后调用setContextConfigLocation(...)这类 setter。registerCustomEditor(Resource.class, ...)注册了自定义编辑器,让 classpath 之类的资源写法能被解析成Resource。bw.setPropertyValues(pvs, true)的第二个参数true表示「不允许未知参数」(ignoreUnknown=true为 false)——也就是说 init-param 名字在 servlet 的 JavaBean 属性里找不到对应 setter 时会直接报错。这是早期 web.xml 配置时代的严格性,注解驱动的现代应用里这段通常几乎不生效。- 模板方法
initServletBean():默认空实现,留给子类。这是整条继承链的第一个扩展点——FrameworkServlet 重写它去建立 WebApplicationContext。
这个设计的本质是:HttpServlet 子类本身被 Spring 当成一个 bean 来装配,用 BeanWrapper 完成了「Servlet 容器的初始化参数 → Spring bean 属性」这一步桥接。
17.3.2 FrameworkServlet.initServletBean:建立 WebApplicationContext
FrameworkServlet(spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java)重写了 initServletBean,它同样是 final:
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
// ... 日志输出耗时
}
initWebApplicationContext()(第 558 行起)做的事,在概念上是:从 ServletContext 拿到根上下文(WebApplicationContextUtils.getWebApplicationContext,即 ContextLoaderListener 建立的那个),再为这个 DispatcherServlet 建立自己的 WebApplicationContext(通常是 XmlWebApplicationContext 或 AnnotationConfigWebApplicationContext),把根上下文设为它的 parent,然后 refresh()。SpringMVC 的 Controller、HandlerMapping 这些 bean 就落在这个 servlet 命名空间的上下文里。initFrameworkServlet() 是另一个空模板方法,给子类(DispatcherServlet 没用到)留的。
关键点:DispatcherServlet 真正的策略初始化(HandlerMapping/HandlerAdapter 等的查找)不是在这一步做的,而是在上下文 refresh 完成后通过 onRefresh(ApplicationContext) 回调做的。这个回调链后面讲。
17.3.3 service 的 PATCH 分支与 doGet/doPost 的统一收口
Servlet 规范的 HttpServlet.service 只会按标准方法分派到 doGet/doPost/doPut/doDelete/doOptions/doTrace。但 PATCH 与 WebDAV 的非标准方法不在其中。FrameworkServlet 重写了 service:
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (HTTP_SERVLET_METHODS.contains(request.getMethod())) {
super.service(request, response);
}
else {
processRequest(request, response);
}
}
HTTP_SERVLET_METHODS 是一个包含 GET/POST/PUT/DELETE/HEAD/OPTIONS/TRACE 的集合。标准方法走父类 service,进而分派到下面这些被声明为 final 的 doXxx:
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
// doPut / doDelete 同样直接 processRequest(request, response)
注意 doGet/doPost/doPut/doDelete 全是 final,且都只做一件事:调用 processRequest。这意味着所有标准方法在 FrameworkServlet 这一层被统一收口到同一个模板方法。doOptions 与 doTrace 略有不同(第 935、964 行):只有当开发者显式打开 dispatchOptionsRequest/dispatchTraceRequest,或者遇到 CORS 预检请求时,才转给 processRequest;否则走 HttpServlet 默认实现。doOptions 里还包了一层 HttpServletResponseWrapper,专门在父类写完 Allow 头后追加 PATCH——这就是为什么默认响应头里能看到 PATCH。
17.3.4 processRequest:线程上下文绑定与清理模板
processRequest 是每个请求的外层包装,它解决的核心问题是:让任意一层业务代码能通过 RequestContextHolder/LocaleContextHolder 静态拿到当前请求,并且在请求结束后把 ThreadLocal 还原。完整源码(第 982-1019 行):
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new ServletException("Request processing failed: " + ex, ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
逐段解读:
- 保存现场:先从两个 Holder 里读出「当前线程上一次绑定的 LocaleContext / RequestAttributes」。这是必要的,因为 Servlet 容器的线程是池化复用的——同一个线程可能先处理过别的请求,甚至在异步dispatch时被复用。
previous*变量记录下来,finally 里原样还原,保证 ThreadLocal 不串请求。 - 构建新上下文:
buildLocaleContext(request)默认返回new SimpleLocaleContext(request.getLocale());DispatcherServlet 重写了它(第 1072 行),改成用localeResolver.resolveLocale(request)——也就是说请求处理过程中 Locale 的真正来源是 DispatcherServlet 的 LocaleResolver 策略,而不是request.getLocale()。 buildRequestAttributes(第 1043-1052 行)有个关键分支:
protected @Nullable ServletRequestAttributes buildRequestAttributes(HttpServletRequest request,
@Nullable HttpServletResponse response, @Nullable RequestAttributes previousAttributes) {
if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) {
return new ServletRequestAttributes(request, response);
}
else {
return null; // preserve the pre-bound RequestAttributes instance
}
}
如果当前线程上已经绑了一个非 ServletRequestAttributes 的 RequestAttributes(比如某个 Filter 提前绑了),这里返回 null,initContextHolders 就不会覆盖它——保护第三方预先绑定的上下文。
4. 异步回调的注册:WebAsyncUtils.getAsyncManager(request) 从 request 属性里取出(或新建)本请求的 WebAsyncManager,并注册一个名为 FrameworkServlet 的 CallableInterceptor(RequestBindingInterceptor)。这个拦截器在异步线程切换时负责在「执行 Callable 之前/之后」重新绑定与还原请求上下文——否则在业务异步线程里 RequestContextHolder.getRequestAttributes() 会是 null。这是异步场景下 ThreadLocal 不丢的关键机制。
5. 核心委托 doService:这是个抽象方法,由 DispatcherServlet 实现。两层 catch 的区别:ServletException|IOException 原样抛出;其他 Throwable(包括 Error)包成 ServletException("Request processing failed: ...") 抛出——这样 @ExceptionHandler 也能接住业务代码里抛出的 Error(doDispatch 里还有一层对 Throwable 的兜底,后面会看到)。
6. finally 清理:resetContextHolders 把 ThreadLocal 还原成 previous*;requestAttributes.requestCompleted() 触发 ServletRequestAttributes 的销毁回调(释放内部缓存的请求/响应对象引用,防止容器线程池持有的 request 属性泄漏);最后 publishRequestHandledEvent 发布一个 ServletRequestHandledEvent(默认开启,可通过 setPublishContext/相关开关关闭以省一次事件发布开销)。
一句话:FrameworkServlet 是「请求级 ThreadLocal 生命周期」的管理者,DispatcherServlet 是「请求级分发逻辑」的实现者。两者通过 processRequest → doService 这个模板方法衔接。
17.3.5 DispatcherServlet.initStrategies:九大策略的装配清单
DispatcherServlet 重写了 onRefresh,把 WebApplicationContext 刷新完成的事件接到自己的策略初始化上:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
这九个 init* 方法的套路高度一致,分两类:
第一类:按固定 bean 名查找,找不到用默认值。 以 initLocaleResolver 为例(第 481-499 行):
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
// ... log
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
}
}
LOCALE_RESOLVER_BEAN_NAME 常量值是 "localeResolver"。getDefaultStrategy 从 DispatcherServlet.properties 里读出该接口的默认实现类名并实例化。initRequestToViewNameTranslator(默认 DefaultRequestToViewNameTranslator)、initFlashMapManager(默认 SessionFlashMapManager)、以及特殊的 initMultipartResolver(默认 null,即不支持文件上传)都属于这一类。
第二类:按类型收集所有实现,排序成列表,找不到则用默认列表。 以 initHandlerMappings 为例(第 506-545 行):
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
for (HandlerMapping mapping : this.handlerMappings) {
if (mapping.usesPathPatterns()) {
this.parseRequestPath = true;
break;
}
}
}
逐段解读:
detectAllHandlerMappings默认 true,意味着「把容器里所有 HandlerMapping bean 都收集来」,并按@Order/Ordered排序。只有当开发者把它设为 false 时,才退回「按 bean 名handlerMapping找单个」的老模式。AnnotationAwareOrderComparator.sort决定了 doDispatch 里getHandler遍历的顺序——这就是为什么自定义 HandlerMapping 想插队必须实现Ordered或加@Order。- 一个关键副作用:如果收集到的任意一个 mapping
usesPathPatterns()返回 true,DispatcherServlet 就把this.parseRequestPath = true。这个标志位会在 doService 里控制「是否提前用PathPatternParser解析并缓存 RequestPath」,见下一小节。 initHandlerAdapters(第 552-584 行)、initHandlerExceptionResolvers(第 591-624 行)、initViewResolvers(第 656-688 行)结构与上面几乎逐行相同,区别只在默认列表与默认值。
7.0.9 的默认策略清单写在 spring-webmvc/src/main/resources/org/springframework/web/servlet/DispatcherServlet.properties 里,原文如下(请以仓库文件为准):
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
要注意:这份默认清单只在「容器里一个同类型 bean 都没有」时才生效。在注解驱动的 Spring Boot/@EnableWebMvc 场景下,WebMvcConfigurationSupport 会主动注册 RequestMappingHandlerMapping、RequestMappingHandlerAdapter 等 bean,因此上面这些默认实现大多被实际的 bean 覆盖。理解这一点,才能理解为什么「查 DispatcherServlet.properties」和「实际运行时看到的策略」可能对不上。
17.3.6 doService:doDispatch 前的准备工作
DispatcherServlet 实现了抽象方法 doService(第 829-879 行):
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath previousRequestPath = null;
if (this.parseRequestPath) {
previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request);
}
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
}
逐段解读:
logRequest:按 debug/trace 级别打印请求方法、URI、参数。注意它对 multipart 只打 "multipart",对 form-urlencoded 默认打 "masked"——这是 7.x 为防敏感信息落日志做的默认保护,可由enableLoggingRequestDetails打开。- include 请求的属性快照:当 DispatcherServlet 被
RequestDispatcher.include调用时,它在原请求的 request 作用域里往里塞了一堆框架属性(WEB_APPLICATION_CONTEXT_ATTRIBUTE等)。快照保证 include 结束后能把这些属性还原回 include 前的状态,避免污染外层 JSP。 - 暴露框架对象:把 WebApplicationContext 与 LocaleResolver 放进 request 属性,View 实现(比如 JSP 标签库)能通过
request.getServletContext()/这些 key 反查到它们。 - FlashMap 恢复:
redirect:场景下,上一个请求通过RedirectAttributes存放的「一次性参数」由flashMapManager.retrieveAndUpdate取出,作为inputFlashMap暴露;同时新建一个空的outputFlashMap供本次请求写入。 - 路径解析缓存:当
parseRequestPath为 true(即至少有一个 mapping 用 PathPattern),doService 提前调用ServletRequestPathUtils.parseAndCache(request)把RequestPath解析结果塞进 request 属性。这样后续 HandlerMapping 查找、路径变量解析共用同一份解析结果,不会重复解析。finally 里再把它还原成之前缓存的那个——同样是为 include/嵌套分发做隔离。 - 异步判断:finally 里若
isConcurrentHandlingStarted()为 true(handler 刚发起了异步处理,主线程即将退出),则不还原属性快照,因为异步恢复dispatch时还要用这些属性。
这一层做完,才真正进入核心的 doDispatch。
17.4 源码剖析(二):doDispatch 全流程逐段拆解
doDispatch(DispatcherServlet.java 第 935-1004 行)是本章最重要的一个方法。先把完整源码摆出来,再逐段拆:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Determine handler adapter and invoke the handler.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new ServletException("Handler dispatch failed: " + err, err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new ServletException("Handler processing failed: " + err, err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
asyncManager.setMultipartRequestParsed(multipartRequestParsed);
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed || asyncManager.isMultipartRequestParsed()) {
cleanupMultipart(processedRequest);
}
}
}
}
这是一个「三层 try-catch-finally」结构,每一层职责不同。逐段解读:
17.4.1 第一层:checkMultipart——文件上传请求的包装
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
checkMultipart(第 1089-1117 行)的完整逻辑:
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
logger.trace("Request already resolved to MultipartHttpServletRequest, for example, by MultipartFilter");
}
}
else if (hasMultipartException(request)) {
logger.debug("Multipart resolution previously failed for current request - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
try {
return this.multipartResolver.resolveMultipart(request);
}
catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}
逐段:
- 前置条件:
multipartResolver == null(默认情况,容器里没有名为multipartResolver的 bean)或isMultipart(request)返回 false(Content-Type 不是multipart/开头),直接返回原 request。这就是为什么「没配 MultipartResolver 时,文件上传接口根本不会被特殊处理」。 - 已经被 MultipartFilter 包过的情况:如果 request 已经是
MultipartHttpServletRequest,直接跳过——避免二次包装。 - 错误分发复用请求:当容器因为之前解析失败而转入 ERROR dispatch 时,request 属性里带着
jakarta.servlet.error.exception;hasMultipartException沿 cause 链找是否有MultipartException,若有则跳过再次解析,让错误页能正常渲染。 - 真正包装:
resolveMultipart(request)返回一个MultipartHttpServletRequest。StandardServletMultipartResolver的实现就是new StandardMultipartHttpServletRequest(request, this.resolveLazily),它内部委托 Servlet 容器的request.getParts()枚举各个 part。
multipartRequestParsed = (processedRequest != request) 这个布尔值很关键:它在 finally 里决定是否调用 cleanupMultipart 清理临时文件。
17.4.2 getHandler:顺序遍历 HandlerMapping
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
getHandler(第 1154-1164 行)非常简单:
protected @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
就是按初始化时排好序的列表,一个一个问「你能处理这个请求吗」,第一个返回非 null 的胜出。注意这里没有「多个 mapping 都匹配时如何取舍」的逻辑——顺序 + 短路。
返回 null 意味着一个 mapping 都没匹配上,此时调用 noHandlerFound(第 1172-1178 行):
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
}
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
7.0.9 直接抛 NoHandlerFoundException(而不是简单地返回 404)。这个异常随后被 doDispatch 的内层 catch 捕获,交给 processHandlerException → 由 DefaultHandlerExceptionResolver 翻译成 404 状态码。这就是「404 是怎么来的」的源码答案。
17.4.3 applyPreHandle:拦截器前置链与短路
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
applyPreHandle 在 HandlerExecutionChain(spring-webmvc/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java 第 142-152 行):
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
逐段:
- 顺序执行所有拦截器的
preHandle。 - 返回 false 的短路:一旦某个
preHandle返回 false,立刻调用triggerAfterCompletion(request, response, null)然后返回 false。注意此时interceptorIndex已经被赋值为「上一个返回 true 的拦截器下标」——也就是说,只回调那些 preHandle 成功过的拦截器的 afterCompletion,连当前这个返回 false 的拦截器自己的 afterCompletion 都不会被调(因为它的 preHandle 没成功)。 - doDispatch 拿到 false 后直接
return——handler 根本不会被调用。开发者的责任是在 preHandle 返回 false 之前自己把响应写好(比如返回 401 JSON)。
17.4.4 getHandlerAdapter 与 ha.handle
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
getHandlerAdapter(第 1185-1195 行):
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
又是一次「顺序遍历 + 第一个 supports 返回 true 胜出」。对注解式 Controller,handler 是 HandlerMethod,RequestMappingHandlerAdapter.supportsInternal 恒返回 true(后面会讲)。
ha.handle(...) 是整个链路最重的一步:它内部完成「把方法参数从请求里解析出来 → 反射调用 Controller 方法 → 处理返回值 → 返回 ModelAndView」。这一步后面用整整两节展开。
调用结束后立即检查 asyncManager.isConcurrentHandlingStarted():如果 handler(比如返回了 Callable/DeferredResult)刚发起了异步处理,主线程直接 return,不再走 applyDefaultViewName/applyPostHandle/processDispatchResult。这是异步模型在 doDispatch 里的第一个分叉点。
17.4.5 applyDefaultViewName 与 applyPostHandle
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
applyDefaultViewName(第 1009-1016 行):
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
if (mv != null && !mv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
mv.setViewName(defaultViewName);
}
}
}
如果 handler 返回的 ModelAndView 既没设视图名也没设 View 对象(!mv.hasView()),就让 RequestToViewNameTranslator 按请求路径推一个默认视图名(默认实现 DefaultRequestToViewNameTranslator 把 /user/list 映射成 user/list)。
applyPostHandle 在 HandlerExecutionChain(第 157-164 行):
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
逆序调用所有拦截器的 postHandle。注意它是从列表末尾往前遍历,不依赖 interceptorIndex——只要 preHandle 全成功了,所有拦截器的 postHandle 都会被调到。postHandle 拿到的 mv 是「handler 调用之后、渲染之前」的模型,开发者可以在这里往 model 里塞公共属性。
17.4.6 异常捕获与 processDispatchResult
内层 try 把整个 handler 执行阶段的异常都收敛成 dispatchException:
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new ServletException("Handler dispatch failed: " + err, err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
注意两层 catch:业务抛的 Exception 原样保留(这样 @ExceptionHandler 能拿到真实异常类型);连 Error(如 NoClassDefFoundError)也包成 ServletException 交给异常解析链。这意味着 handler 里抛的任何东西都不会直接穿透到容器,而是进入 processDispatchResult 统一处理。
17.4.7 外层 catch 与 finally
外层 try 的 catch 专门兜「processDispatchResult 自己都没救回来的异常」——比如视图渲染失败、异常 resolver 链都解析不了。此时调用 triggerAfterCompletion(第 1357-1364 行):
private static void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, ex);
}
throw ex;
}
它先把已成功 preHandle 的拦截器的 afterCompletion 跑完,再把异常原样抛出(让容器记录)。
finally 块处理两种收尾:
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
asyncManager.setMultipartRequestParsed(multipartRequestParsed);
}
else {
if (multipartRequestParsed || asyncManager.isMultipartRequestParsed()) {
cleanupMultipart(processedRequest);
}
}
- 异步刚启动:调用每个
AsyncHandlerInterceptor的afterConcurrentHandlingStarted(逆序,见HandlerExecutionChain.applyAfterConcurrentHandlingStarted第 186-200 行),并把「multipart 是否已解析」这个状态交给 asyncManager,因为异步恢复时还要用。 - 同步完成:若之前包装过多 part,调用
cleanupMultipart(第 1138-1146 行),它最终调到StandardServletMultipartResolver.cleanupMultipart,把每个Part的临时文件part.delete()掉。
这就是 doDispatch 的完整因果链。下一节看 processDispatchResult 如何处理异常与渲染。
17.4.8 processDispatchResult:异常解析与视图渲染的统一出口
doDispatch 内层 try 结束后,无论成功还是异常,最后都走到 processDispatchResult(第 1022-1062 行):
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException mavDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = mavDefiningException.getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
逐段:
- 两种异常分支:
ModelAndViewDefiningException是一种特殊异常——它本身携带一个 ModelAndView,常用于拦截器或视图渲染前就想直接跳到某个错误页的场景,直接取出它的 mav;其他异常走processHandlerException。 errorView = (mv != null):如果异常被某个 resolver 成功解析成了一个非空 ModelAndView,就标记为 errorView。渲染完之后调用WebUtils.clearErrorRequestAttributes,把 JSP 错误页常用的jakarta.servlet.error.*属性清掉,避免泄漏到后续请求。mv != null && !mv.wasCleared()才渲染:两个条件。mv == null的典型场景是@ResponseBody——返回值处理器已经把响应体写出去了,mavContainer 被标记为 requestHandled,getModelAndView返回 null;wasCleared()则是 model 被显式清空的情况。两者都不渲染。- 异步二次分叉:如果在渲染(比如视图里又发起了异步)过程中异步启动了,直接 return,不再 afterCompletion。
- 正常完成后的 afterCompletion:注意这里传入的 exception 是 null——代表「成功走完的拦截器,afterCompletion 时异常参数为 null」。只有外层 catch 路径(渲染本身失败)才会把真实异常传进去。
17.4.9 processHandlerException:HandlerExceptionResolver 链
processHandlerException(第 1207-1256 行)是异常解析的核心:
protected @Nullable ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Reset the response content-type header and body buffer if the response is not committed already,
// leaving the other response headers in place.
try {
response.setHeader(HttpHeaders.CONTENT_TYPE, null);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, null);
response.resetBuffer();
}
catch (IllegalStateException illegalStateException) {
// the response is already committed, leave it to exception handlers anyway
}
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
逐段:
- 清场:异常发生在 handler 执行中途,此时 response 可能已经被 handler 设过 Content-Type、写过 buffer。代码先移除「可生产媒体类型」属性、把 Content-Type/Content-Disposition 头置 null、并
resetBuffer()。但只在 response 未提交时有效——如果 response 已提交(比如已经 flush 了半个 JSON),resetBuffer会抛IllegalStateException,被 catch 住,然后「留给异常处理器自己看着办」。这是为什么「响应已提交后抛异常」往往无法再换成错误页。 - resolver 链顺序遍历:第一个返回非 null exMv 的 resolver 胜出。顺序由初始化时的
AnnotationAwareOrderComparator.sort决定。 - 空 ModelAndView 的特殊含义:
exMv.isEmpty()表示「这个 resolver 已经自己把响应处理完了(比如直接写了 JSON、设了状态码),不需要再渲染视图」。此时把异常放进 request 属性EXCEPTION_ATTRIBUTE(key 是exception),返回 null——doDispatch 随后就不会 render。 - 错误模型的视图名翻译:如果 resolver 只返回了一个 model 而没给视图名,同样走
getDefaultViewName。 exposeErrorRequestAttributes:把异常、servletName 等暴露成jakarta.servlet.error.*request 属性,供 JSP 错误页使用。- 所有 resolver 都没接住:
throw ex——重新抛出,进入 doDispatch 外层 catch,最终跑到容器的错误页机制。
17.4.10 render:视图名解析与 View.render
render(第 1267-1313 行):
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
if (view instanceof SmartView smartView) {
smartView.resolveNestedViews(this::resolveViewNameInternal, locale);
}
try {
if (mv.getStatus() != null) {
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
逐段:
- 先设 Locale:通过
localeResolver.resolveLocale(request)拿到当前区域,设到 response。 - 两种 mv 形态:带视图名字符串 →
resolveViewName走 ViewResolver 链;带现成View实例(比如 handler 直接返回了View对象,或者RedirectView)→ 直接用。 resolveViewName→resolveViewNameInternal(第 1345-1355 行):就是顺序遍历viewResolvers,第一个返回非 null View 的胜出。SmartView嵌套解析:某些视图(如RedirectView)可能内部还引用别的视图名,让它自己解析。- ModelAndView 上的状态码:如果 mv 带了 status(比如
ResponseEntity转换过来的),先设到 response,再渲染。 view.render(model, request, response):这是真正把模型数据塞进 request、forward 到 JSP 或写出 CSV/JSON 的一步。对InternalResourceView来说,render 内部就是把 model 的每个 key-valuerequest.setAttribute,然后request.getRequestDispatcher(forwardUrl).forward(request, response)。
17.4.11 拦截器 afterCompletion 的逆序回调细节
processDispatchResult 末尾的 mappedHandler.triggerAfterCompletion(request, response, null),其实现(HandlerExecutionChain.java 第 171-181 行):
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception in interceptor [" + interceptor + "]", ex2);
}
}
}
两个设计要点:
- 从
interceptorIndex开始逆序:这就是为什么「只有 preHandle 成功过的拦截器才会被 afterCompletion 回调」。interceptorIndex在 applyPreHandle 每成功一个就更新为 i;preHandle 返回 false 时调用triggerAfterCompletion,此时 index 停留在上一个成功的位置。 - 每个 afterCompletion 单独 try-catch:某个拦截器的 afterCompletion 抛异常,只记日志,不影响其他拦截器的回调。这是资源清理场景的必要容错——否则一个拦截器的清理异常会中断后续拦截器的清理。
对比 applyPostHandle:postHandle 是「从列表末尾逆序遍历所有」,不看 interceptorIndex。所以「preHandle 成功过的拦截器集合」与「postHandle 会被调用的拦截器集合」在正常情况下是一致的(全成功),但在 preHandle 中途短路时,postHandle 根本不会执行(doDispatch 直接 return 了)。
17.5 源码剖析(三):HandlerMapping 体系——如何把 URL 翻译成 HandlerExecutionChain
doDispatch 里 getHandler 只是顺序遍历,真正的功夫在每个 HandlerMapping.getHandler 内部。这一节拆 AbstractHandlerMapping 与 RequestMappingHandlerMapping。
17.5.1 AbstractHandlerMapping.getHandler:模板方法骨架
AbstractHandlerMapping(spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java)把 getHandler 实现为 final,固定了骨架(第 542-591 行):
@Override
public final @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
ApiVersionHolder versionHolder = initApiVersion(request);
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
if (versionHolder.hasError() && !request.getDispatcherType().equals(DispatcherType.ERROR)) {
throw versionHolder.getError();
}
// Bean name or resolved handler?
if (handler instanceof String handlerName) {
handler = obtainApplicationContext().getBean(handlerName);
}
// Ensure presence of cached lookupPath for interceptors and others
if (!ServletRequestPathUtils.hasCachedPath(request)) {
initLookupPath(request);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (request.getAttribute(SUPPRESS_LOGGING_ATTRIBUTE) == null) {
// ... trace/debug 日志 "Mapped to ..."
}
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
if (config != null) {
config.validateAllowCredentials();
config.validateAllowPrivateNetwork();
}
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
逐段解读:
- API 版本解析(
initApiVersion,第 593-609 行):如果配置了apiVersionStrategy(7.0 引入的版本号协商机制,从 header/path/参数里解析 API 版本),先解析;解析出错且不是 ERROR dispatch,直接抛异常。 getHandlerInternal抽象方法:子类(如RequestMappingHandlerMapping)实现「在自己的注册表里找 handler」。返回 null 则用getDefaultHandler(),再 null 就返回 null(让 DispatcherServlet 走 noHandlerFound)。- handler 可能是字符串:
BeanNameUrlHandlerMapping返回的 handler 是一个 bean 名字符串,这里通过obtainApplicationContext().getBean(handlerName)解析成真正的 bean 实例。这是「handler 形态多态」的第一个体现。 initLookupPath(第 641-651 行):如果 usesPathPatterns,从已缓存的RequestPath里取pathWithinApplication();否则用UrlPathHelper.resolveAndCacheLookupPath。lookupPath 是后续MappedInterceptor.matches判断拦截器是否适用的依据。getHandlerExecutionChain(handler, request):把裸 handler 包成 chain 并装配拦截器,下一小节展开。- CORS 处理:如果 handler 带 CORS 配置,或这是一个预检请求(OPTIONS),取出配置(局部 + 全局合并),然后通过
getCorsHandlerExecutionChain给 chain 动态插入一个CorsInterceptor;对预检请求,handler 会被替换成PreFlightHandler(一个专门处理 CORS 预检的 HttpRequestHandler),根本不会调到你的 Controller 方法。
17.5.2 getHandlerExecutionChain:拦截器是怎么装进去的
第 681-706 行:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain handlerExecutionChain ?
handlerExecutionChain : new HandlerExecutionChain(handler));
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor mappedInterceptor) {
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
if (this.versionStrategy != null) {
ApiVersionHolder versionHolder = (ApiVersionHolder) request.getAttribute(API_VERSION_ATTRIBUTE);
if (versionHolder.hasVersion()) {
Comparable<?> version = versionHolder.getVersion();
chain.addInterceptor(new ApiVersionDeprecationHandlerInterceptor(this.versionStrategy, version));
}
}
return chain;
}
逐段:
- adaptedInterceptors 从哪来?
AbstractHandlerMapping在initInterceptors()(第 489 行起)里把两类东西合并:setInterceptors(...)直接配的拦截器,以及所有实现了MappedInterceptor(带 path pattern)的 bean。adaptInterceptor(第 513 行)会把WebRequestInterceptor包装成InterceptorRegistration风格的WebRequestHandlerInterceptorAdapter。 - MappedInterceptor 的 path 匹配:普通
HandlerInterceptor无条件加入 chain;MappedInterceptor(通过addPathPatterns/excludePathPatterns注册的那种)要先matches(request)——依据 lookupPath 判断当前请求是否在拦截范围内。这就是为什么「配置了 addPathPatterns 的拦截器只对部分 URL 生效」。 - 7.0 的版本弃用拦截器:如果请求解析出了 API 版本,且该版本被标记为弃用,追加一个
ApiVersionDeprecationHandlerInterceptor,在响应里加Deprecation/Sunset头。
17.5.3 RequestMappingHandlerMapping:isHandler 与 getMappingForMethod
这是注解式路由的注册中心。它继承 RequestMappingInfoHandlerMapping → AbstractHandlerMethodMapping → AbstractHandlerMapping。启动时 afterPropertiesSet(继承自 AbstractHandlerMethodMapping)会扫描容器里所有 bean,调用模板方法 isHandler(beanType) 判断「这个 bean 是不是 Controller」。
7.0.9 的 isHandler 实现(RequestMappingHandlerMapping.java 第 181-183 行):
@Override
protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
}
这是一个与旧版本显著不同的点:在 5.x 及更早版本中,这个判断还包含 AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)——也就是说,一个只在类上打了 @RequestMapping、没打 @Controller 的类,曾经也会被当作 handler。7.0.9 收窄为「只有带 @Controller 的类才是 handler」。注意 @RequestMapping 本身被 @Controller 元注解标注?不——@Controller 是 stereotype,@RestController 是 @Controller + @ResponseBody 的组合注解,所以 @RestController 类也能被 isHandler 命中。
被判定为 handler 后,AbstractHandlerMethodMapping 会枚举类里的每个方法,调用 getMappingForMethod(第 204-222 行):
@Override
protected @Nullable RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
validateCglibProxyMethodVisibility(method, handlerType);
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
if (info.isEmptyMapping()) {
info = info.mutate().paths("", "/").options(this.config).build();
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
逐段:
validateCglibProxyMethodVisibility(7.0 新增,第 228-254 行):如果 handler bean 是 CGLIB 代理(类名含$$),handler 方法是 private 或跨包 package-private,直接抛IllegalStateException。这是 7.0 为了避免「代理类上 private 方法被误当 handler」而做的强校验。- 方法级 + 类级合并:先读方法上的
@RequestMapping/@HttpExchange得到 info;再读类上的,typeInfo.combine(info)把类路径拼到方法路径前面。 - 空映射兜底:类上有
@Controller但方法没标任何 mapping 且方法本身是默认(无参)的情况,补成空路径。 getPathPrefix:支持全局路径前缀(PathMatchConfigurer.addPathPrefix),拼到最前面。
生成的 RequestMappingInfo(包含路径模式、HTTP method、consumes、produces、params、headers 等条件)与 HandlerMethod 一起,被注册进 AbstractHandlerMethodMapping 内部的 mappingRegistry(一个 MappingRegistry,核心是 Map<T, HandlerMethod> 加上路径匹配器)。运行时请求进来,RequestMappingInfoHandlerMapping.getHandlerInternal 用当前 lookupPath 去匹配,遍历所有注册过的 RequestMappingInfo,选出「路径匹配且 method/consumes/produces 全部满足」的那一个。
17.5.4 路径匹配:7.0.9 默认就是 PathPatternParser
一个常见的疑问:「7.0 是不是默认用 PathPattern 而不是 AntPathMatcher?」看 RequestMappingHandlerMapping.afterPropertiesSet(第 146-159 行):
@Override
@SuppressWarnings("removal")
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setContentNegotiationManager(getContentNegotiationManager());
this.config.setApiVersionStrategy(getApiVersionStrategy());
if (getPatternParser() != null) {
this.config.setPatternParser(getPatternParser());
}
else {
this.config.setPathMatcher(getPathMatcher());
}
super.afterPropertiesSet();
}
这里是「二选一」逻辑。那默认 getPatternParser() 是什么?答案在 WebMvcConfigurationSupport.initHandlerMapping(第 492-513 行):
private void initHandlerMapping(
@Nullable AbstractHandlerMapping mapping, FormattingConversionService conversionService,
ResourceUrlProvider resourceUrlProvider) {
if (mapping == null) {
return;
}
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
if (pathConfig.preferPathMatcher()) {
mapping.setPatternParser(null);
mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
}
else if (pathConfig.getPatternParser() != null) {
mapping.setPatternParser(pathConfig.getPatternParser());
}
// else: AbstractHandlerMapping defaults to PathPatternParser
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setCorsConfigurations(getCorsConfigurations());
}
注释写得很明白:否则(既没显式 preferPathMatcher、也没显式设 patternParser),AbstractHandlerMapping 默认就是 PathPatternParser。也就是说,7.0.9 的注解式请求映射默认走的是 PathPatternParser(基于解析后的 RequestPath,支持高效的匹配与路径变量提取),只有显式 spring.mvc.pathmatch.matching-strategy=ant_path_matcher 或代码里 preferPathMatcher() 才回退到老的 AntPathMatcher。这与 doService 里 parseRequestPath 标志位、ServletRequestPathUtils.parseAndCache(request) 是同一套机制——路径在请求一开始就被解析成 RequestPath,后续所有映射都在这份解析结果上做匹配。
17.6 源码剖析(四):HandlerAdapter 与参数解析
找到 chain 之后,doDispatch 调用 ha.handle(...)。对注解式 Controller,ha 就是 RequestMappingHandlerAdapter。这一节拆它如何把一个 HTTP 请求变成方法实参、反射调用、再把返回值交给处理器。
17.6.1 supports 恒为 true:为什么任何 HandlerMethod 都能被适配
RequestMappingHandlerAdapter 继承 AbstractHandlerMethodAdapter,后者的 supports 最终调到 supportsInternal(第 825-828 行):
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
注释解释了设计意图:任何方法参数和返回值都会以某种方式被处理——没被识别的简单类型当请求参数,没被识别的复合类型当 model attribute,没被识别的返回值当 model attribute。这是一种「宽容兜底」策略,而不是严格拒绝。
17.6.2 handleInternal:session 同步与响应头准备
第 831-866 行:
@Override
protected @Nullable ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
两个要点:
synchronizeOnSession:默认 false。打开后,同一个 HttpSession 内的请求会按 session 互斥锁串行执行——这是早期「有状态 Controller」时代的遗留开关,注解式 Controller 无状态,一般不开。- 缓存响应头准备:如果 handler 类标了
@SessionAttributes,说明它在往 session 里放模型属性,为了防止浏览器缓存旧的 session 数据,自动加一组 cache-control 头;否则prepareResponse加默认的Cache-Control: no-store。
17.6.3 invokeHandlerMethod:异步管理器、模型工厂与 ServletInvocableHandlerMethod
第 885-940 行是适配器最核心的一段:
protected @Nullable ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
// Obtain wrapped response to enforce lifecycle rule from Servlet spec, section 2.3.3.4
response = asyncWebRequest.getNativeResponse(HttpServletResponse.class);
ServletWebRequest webRequest = (asyncWebRequest instanceof ServletWebRequest ?
(ServletWebRequest) asyncWebRequest : new ServletWebRequest(request, response));
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
invocableMethod.setMethodValidator(this.methodValidator);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
Object[] resultContext = asyncManager.getConcurrentResultContext();
Assert.state(resultContext != null && resultContext.length > 0, "Missing result context");
mavContainer = (ModelAndViewContainer) resultContext[0];
asyncManager.clearConcurrentResult();
// ... log "Resume with async result"
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
逐段:
- 异步基础设施:每个请求都建立(或复用)
AsyncWebRequest,设超时;把 taskExecutor、callable/deferredResult 拦截器注册进 asyncManager。注意response = asyncWebRequest.getNativeResponse(...)——它包了一层 response,强制 Servlet 2.3.3.4 生命周期规则(异步开始后再写 response 会被拦截)。 WebDataBinderFactory:为这个 handler 准备@InitBinder方法的数据绑定器工厂。ModelFactory:聚合两类「模型预填充」方法——@ControllerAdvice里的@ModelAttribute方法(全局优先),和本 controller 里的@ModelAttribute方法。modelFactory.initModel(...)在调 handler 方法之前先把这些属性放进 mavContainer。这就是为什么「@ModelAttribute方法会在每个请求、每个 handler 方法执行前先跑一遍」。ServletInvocableHandlerMethod注入全套依赖:参数解析器、返回值处理器、数据绑定工厂、参数名发现器、方法校验器。ModelAndViewContainer:一个「可变的模型 + 视图 + requestHandled 标志」容器。它不是 ModelAndView——真正的 ModelAndView 是在最后getModelAndView时从它提炼出来的。- 异步恢复分支:如果
hasConcurrentResult()为 true,说明这是异步结果恢复的 dispatch。此时 mavContainer 直接用上一次保存的(resultContext[0]),invocableMethod 被wrapConcurrentResult(result)包成一个「直接返回异步结果」的方法对象——也就是说,异步恢复时并不是再调一次原 Controller 方法,而是把异步结果当成「方法返回值」直接走返回值处理器。 invokeAndHandle:真正调方法 + 处理返回值。如果刚发起异步,返回 null(doDispatch 看到 isConcurrentHandlingStarted 就直接 return);否则getModelAndView把 mavContainer 转成 ModelAndView 返回给 doDispatch。
17.6.4 内置参数解析器清单(以 getDefaultArgumentResolvers 为准)
参数解析器的装配在 RequestMappingHandlerAdapter.getDefaultArgumentResolvers()(第 644-690 行)。7.0.9 的完整清单(按顺序):
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
resolvers.add(new ApiVersionMethodArgumentResolver());
if (KotlinDetector.isKotlinPresent()) {
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
}
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
按职责分四组讲解:
- 注解驱动组(15 个):
RequestParamMethodArgumentResolver(false)处理@RequestParam;@RequestParam Map由RequestParamMapMethodArgumentResolver;@PathVariable与@PathVariable Map由一对处理器;@MatrixVariable一对;@RequestBody由RequestResponseBodyMethodProcessor(它同时也是返回值处理器,后面讲);@RequestPart(multipart 表单里的文件/JSON part)由RequestPartMethodArgumentResolver;@RequestHeader一对;@CookieValue由ServletCookieValueMethodArgumentResolver;@Value表达式由ExpressionValueMethodArgumentResolver;@SessionAttribute/@RequestAttribute各一个。 - 类型驱动组(10 个):直接按参数类型识别,与注解无关。
ServletRequestMethodArgumentResolver认HttpServletRequest、MultipartRequest、ServletWebRequest等;ServletResponseMethodArgumentResolver认HttpServletResponse、OutputStream、Writer;HttpEntityMethodProcessor认HttpEntity<X>/RequestEntity<X>;RedirectAttributesMethodArgumentResolver、ModelMethodProcessor、MapMethodProcessor(往 model 里塞数据)、ErrorsMethodArgumentResolver(校验错误对象,必须紧跟在@Validated参数后面)、SessionStatusMethodArgumentResolver、UriComponentsBuilderMethodArgumentResolver;以及 7.0 新增的ApiVersionMethodArgumentResolver(注入当前解析出的 API 版本)。 - Catch-all 组(3 个,最后):
PrincipalMethodArgumentResolver(认java.security.Principal)、RequestParamMethodArgumentResolver(true)(兜底:没带任何注解的简单类型——int/long/String/Date 等——一律当请求参数处理)、ServletModelAttributeMethodProcessor(true)(兜底:没带注解的复合类型,当 model attribute 处理,会走数据绑定与校验)。
顺序极其重要:因为 Composite 是「第一个 supportsParameter 返回 true 的胜出」。例如一个没注解的 String 参数,会被 catch-all 的 RequestParamMethodArgumentResolver(true) 接住(当 query 参数),而不是被后面的 model attribute 处理器接住。这也是为什么「一个没加 @RequestParam 的 String 形参会自动从 query string 取值」——它不是魔法,是这个兜底解析器在起作用。
17.6.5 getMethodArgumentValues:逐参数解析与反射调用
参数解析的执行在 InvocableHandlerMethod(spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java)。注意这个类在 spring-web 而不是 spring-webmvc——它被 WebMVC 与 messaging 模块共用。
invokeForRequest(第 171-192 行):
public @Nullable Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
@Nullable Object... providedArgs) throws Exception {
@Nullable Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
if (shouldValidateArguments() && this.methodValidator != null) {
this.methodValidator.applyArgumentValidation(
getBean(), getBridgedMethod(), getMethodParameters(), args, getValidationGroups());
}
Object returnValue = doInvoke(args);
if (shouldValidateReturnValue() && this.methodValidator != null) {
this.methodValidator.applyReturnValueValidation(
getBean(), getBridgedMethod(), getReturnType(), returnValue, getValidationGroups());
}
return returnValue;
}
getMethodArgumentValues(第 200-238 行):
protected @Nullable Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
@Nullable Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
@Nullable Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (parameter.getParameterType().equals(HandlerMethod.class) && parameter.isOptional()) {
args[i] = null;
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
逐段:
- providedArgs 优先:
findProvidedArgument先看是否有「按类型直接给」的参数(比如ServletRequestMethodArgumentResolver处理的 HttpServletRequest 由上层直接传入),有就不用走 resolver。 - 参数名发现:
initParameterNameDiscovery让 MethodParameter 在需要时通过ParameterNameDiscoverer(基于 ASM 读 class 元数据)拿到arg0之外的真实参数名——这是@RequestParam不写 value 时能自动用形参名的前提。 - Composite 的缓存:
this.resolvers.supportsParameter与resolveArgument都委托给HandlerMethodArgumentResolverComposite。它内部有个ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache——第一次解析某个参数后,「哪个 resolver 负责它」被缓存,后续请求同类型方法的同位置参数直接命中缓存,不再线性遍历 20 多个 resolver。 - 无 resolver 兜底失败:如果连 catch-all 都认不出该参数(比如一个既不是简单类型也不是 model attribute 的奇怪类型),抛
IllegalStateException("No suitable resolver")——这是启动后第一个请求就可能爆的错。
doInvoke(第 243-276 行):
protected @Nullable Object doInvoke(@Nullable Object... args) throws Exception {
Method method = getBridgedMethod();
try {
if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(method.getDeclaringClass())) {
if (KotlinDetector.isSuspendingFunction(method)) {
return invokeSuspendingFunction(method, getBean(), args);
}
return KotlinDelegate.invokeFunction(method, getBean(), args);
}
return method.invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
// ... 格式化错误信息
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getCause();
if (targetException instanceof RuntimeException runtimeException) {
throw runtimeException;
}
else if (targetException instanceof Error error) {
throw error;
}
else if (targetException instanceof Exception exception) {
throw exception;
}
else {
throw new IllegalStateException(formatInvokeError("Invocation failure", targetException));
}
}
}
关键细节:method.invoke 抛的 InvocationTargetException 会把业务真实异常包在 cause 里。这里直接拆包——RuntimeException/Error/Exception 原样抛出。这就是为什么 handler 里抛的业务异常(比如 IllegalArgumentException)能直接被 @ExceptionHandler 接住,而不是一个 InvocationTargetException。
17.7 源码剖析(五):返回值处理与 HttpMessageConverter 调用链
方法反射调用拿到返回值之后,下一站是 ServletInvocableHandlerMethod.invokeAndHandle。它决定「这个返回值最终是走视图渲染,还是直接写消息体」。
17.7.1 invokeAndHandle:返回值分派的总闸
ServletInvocableHandlerMethod(spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java 第 114-144 行):
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
@Nullable Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
逐段:
- 先
invokeForRequest拿到返回值(上一节讲过的参数解析 + 反射调用)。 setResponseStatus(第 149-168 行):读方法上的@ResponseStatus注解。有 reason 则response.sendError(code, reason);否则response.setStatus(code);同时把状态码放进 request 属性View.RESPONSE_STATUS_ATTRIBUTE(供RedirectView等使用)。- 返回值为 null 的三种「直接结束」情形:304 Not Modified、已设状态码、或 mavContainer 已被标记为 requestHandled(比如
@ResponseBody返回 null)——此时把requestHandled设为 true 并 return,不再走返回值处理器。这就是为什么@ResponseBody方法返回 null 不会触发视图渲染。 @ResponseStatus带 reason 但返回值非 null:直接 requestHandled,不处理返回值。- 否则交给 Composite 返回值处理器:
handleReturnValue内部按顺序找第一个supportsReturnType返回 true 的处理器。
17.7.2 内置返回值处理器清单
getDefaultReturnValueHandlers(第 730-776 行):
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);
ResponseBodyEmitterReturnValueHandler responseBodyEmitterHandler =
new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager,
initViewResolvers(), initLocaleResolver());
HttpEntityMethodProcessor httpEntityMethodProcessor = new HttpEntityMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice, this.errorResponseInterceptors);
// Single-purpose return value types
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(responseBodyEmitterHandler);
handlers.add(new StreamingResponseBodyReturnValueHandler());
handlers.add(new ResponseEntityReturnValueHandler(httpEntityMethodProcessor, responseBodyEmitterHandler));
handlers.add(new HttpHeadersReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
// Annotation-based return value types
handlers.add(new ServletModelAttributeMethodProcessor(false));
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice, this.errorResponseInterceptors));
// Multi-purpose return value types
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
// Custom return value types
if (getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}
// Catch-all
if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
}
else {
handlers.add(new ServletModelAttributeMethodProcessor(true));
}
return handlers;
}
按组理解:
- 单用途组:返回
ModelAndView由ModelAndViewMethodReturnValueHandler处理;返回Model/Map由ModelMethodProcessor;返回View由ViewMethodReturnValueHandler;返回ResponseBodyEmitter/StreamingResponseBody/ResponseEntity/HttpHeaders/Callable/DeferredResult/WebAsyncTask各有专属处理器。 - 注解组:
@ModelAttribute但没@ResponseBody的由ServletModelAttributeMethodProcessor(false)(把返回值塞进 model);@ResponseBody由RequestResponseBodyMethodProcessor——注意它既是参数解析器又是返回值处理器。 - 多用途组:返回字符串且不是
@ResponseBody的,由ViewNameMethodReturnValueHandler解释为视图名;返回Map塞 model。 - Catch-all:最后那个
ServletModelAttributeMethodProcessor(true)——任何没被前面处理器认出来的返回值,一律塞进 model 当 model attribute。这就是为什么「一个普通 POJO 作为返回值且没加任何注解」会被当成 model attribute 暴露给视图。
HandlerMethodReturnValueHandlerComposite.handleReturnValue(spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodReturnValueHandlerComposite.java 第 70-91 行):
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
private @Nullable HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
注意它与参数 Composite 的区别:返回值 Composite 没有缓存——因为返回值类型组合多、且每次请求都要重新判断。另外有个 isAsyncValue 短路:如果返回值是异步类型(DeferredResult/Callable 等),跳过所有非异步处理器,确保走到 DeferredResultMethodReturnValueHandler 这类异步处理器。
17.7.3 RequestResponseBodyMethodProcessor:进入消息转换器
@ResponseBody 的处理最终调到 AbstractMessageConverterMethodProcessor.writeWithMessageConverters(spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java 第 205-368 行)。这是消息协商与转换器选择的核心。节选关键段:
MediaType selectedMediaType = null;
MediaType contentType = outputMessage.getHeaders().getContentType();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
if (isContentTypePreset) {
selectedMediaType = contentType;
}
else {
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes;
try {
acceptableTypes = getAcceptableMediaTypes(request);
}
catch (HttpMediaTypeNotAcceptableException ex) {
// 4xx/5xx 错误响应时忽略协商异常,直接 return
}
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
List<MediaType> compatibleMediaTypes = determineCompatibleMediaTypes(acceptableTypes, producibleTypes);
// For ProblemDetail, fall back on RFC 9457 format
if (compatibleMediaTypes.isEmpty() && ProblemDetail.class.isAssignableFrom(valueType)) {
compatibleMediaTypes = determineCompatibleMediaTypes(PROBLEM_MEDIA_TYPES, producibleTypes);
}
if (compatibleMediaTypes.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
return;
}
MimeTypeUtils.sortBySpecificity(compatibleMediaTypes);
for (MediaType mediaType : compatibleMediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
ResolvableType targetResolvableType = null;
for (HttpMessageConverter converter : this.messageConverters) {
ConverterType converterTypeToUse = null;
if (converter instanceof GenericHttpMessageConverter genericConverter) {
if (genericConverter.canWrite(targetType, valueType, selectedMediaType)) {
converterTypeToUse = ConverterType.GENERIC;
}
}
else if (converter instanceof SmartHttpMessageConverter smartConverter) {
targetResolvableType = getNestedTypeIfNeeded(ResolvableType.forType(targetType));
if (smartConverter.canWrite(targetResolvableType, valueType, selectedMediaType)) {
converterTypeToUse = ConverterType.SMART;
}
}
else if (converter.canWrite(valueType, selectedMediaType)){
converterTypeToUse = ConverterType.BASE;
}
if (converterTypeToUse != null) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(), inputMessage, outputMessage);
if (body != null) {
addContentDispositionHeader(inputMessage, outputMessage);
switch (converterTypeToUse) {
case BASE -> converter.write(body, selectedMediaType, outputMessage);
case GENERIC -> ((GenericHttpMessageConverter) converter).write(body, targetType, selectedMediaType, outputMessage);
case SMART -> ((SmartHttpMessageConverter) converter).write(body, targetResolvableType,
selectedMediaType, outputMessage, getAdvice().determineWriteHints(...));
}
}
return;
}
}
}
逐段:
- Content-Type 预设优先:如果 response 已经被显式 setContentType 且是具体类型(比如 handler 里自己设了
application/pdf),直接用它,跳过协商。 - acceptableTypes vs producibleTypes:
getAcceptableMediaTypes从Accept头(通过 ContentNegotiationManager)解析出客户端能接受的类型;getProducibleMediaTypes算出「当前有哪些转换器能写这个 valueType」。两者取交集得到 compatibleMediaTypes。 - produces 注解的作用:
@RequestMapping(produces="application/json")会把 producibleTypes 收窄到 json——这就是为什么 produces 能强制返回类型。 - 协商失败:compatibleMediaTypes 为空且 body 非 null,抛
HttpMediaTypeNotAcceptableException(406)。ProblemDetail 有特殊兜底(RFC 9457 的 application/problem+json)。 - 按 specificity 排序选最具体的:
MimeTypeUtils.sortBySpecificity后,挑第一个具体类型;如果是*/*这种泛类型,回退成 octet-stream。 - 转换器遍历:按
messageConverters列表顺序,第一个canWrite返回 true 的胜出。三种 canWrite 分支对应三种转换器接口:老的HttpMessageConverter(BASE)、带泛型的GenericHttpMessageConverter(GENERIC)、7.0 引入的SmartHttpMessageConverter(SMART,带 hints)。 beforeBodyWrite钩子:在真正 write 之前,调RequestBodyAdvice.beforeBodyWrite——这是全局请求体增强的扩展点。- 写出去:按分支调对应 write 方法,然后 return(找到第一个就停)。遍历完所有 converter 都没 canWrite 的,最后抛
HttpMessageNotWritableException(500)或HttpMediaTypeNotAcceptableException。
17.7.4 AbstractHttpMessageConverter:canRead/canWrite/read/write 模板
AbstractHttpMessageConverter<T>(spring-web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java)是绝大多数具体转换器的基类。canRead/canWrite(第 132-186 行):
@Override
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
return supports(clazz) && canRead(mediaType);
}
protected boolean canRead(@Nullable MediaType mediaType) {
if (mediaType == null) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.includes(mediaType)) {
return true;
}
}
return false;
}
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
return supports(clazz) && canWrite(mediaType);
}
protected boolean canWrite(@Nullable MediaType mediaType) {
if (mediaType == null || MediaType.ALL.equalsTypeAndSubtype(mediaType)) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.isCompatibleWith(mediaType)) {
return true;
}
}
return false;
}
模板方法 read/write(第 193-235 行):
@Override
public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return readInternal(clazz, inputMessage);
}
@Override
public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage streamingOutputMessage) {
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
writeInternal(t, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
});
}
@Override
public boolean repeatable() {
return supportsRepeatableWrites(t);
}
});
}
else {
writeInternal(t, outputMessage);
outputMessage.getBody().flush();
}
}
要点:
supports(clazz)由子类实现(判断能不能处理这个 Java 类型);canRead/canWrite(MediaType)由基类实现(遍历supportedMediaTypes)。两者与运算才是完整的 canRead。- read 是 final:模板方法模式,子类只能实现
readInternal/writeInternal。 - write 处理流式输出:如果 outputMessage 是
StreamingHttpOutputMessage(用于ResponseBodyEmitter/SseEmitter这种边生成边写的场景),不立即写,而是把writeInternal包成一个 Body 留给容器在异步阶段调用;否则同步写并 flush。 addDefaultHeaders(第 244-271 行):没设 Content-Type 时取getDefaultContentType(默认是 supportedMediaTypes 的第一个),并附加 defaultCharset、Content-Length。
17.7.5 几个常用转换器的默认 supportedMediaTypes
StringHttpMessageConverter(spring-web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java):构造函数super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL),默认 charset 是ISO_8859_1(第 51 行DEFAULT_CHARSET = StandardCharsets.ISO_8859_1)。也就是说,默认情况下@ResponseBody返回 String 时 Content-Type 是text/plain;charset=ISO-8859-1——中文会乱码,这是历史遗留,需要显式配 charset 或用 produces。ByteArrayHttpMessageConverter:super(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL)——字节数组默认当 octet-stream。MappingJackson2HttpMessageConverter(spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java):默认 supportedMediaTypes 包含application/json、application/*+json。canWrite 命中后,writeInternal 内部用 JacksonObjectMapper.writeValue把对象序列化成 JSON 字节流。
7.0.9 服务端默认转换器注册顺序由 HttpMessageConverters.forServer() 文档明确列出(spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java 第 67-84 行):ByteArray → String → Resource → ResourceRegion → Kotlin Serialization JSON → JSON(Jackson)→ Smile → Kotlin CBOR → CBOR → YAML → XML → Protobuf → ATOM/RSS → Multipart。这个顺序决定了「谁先被 canWrite 命中」:比如返回 String 时,StringHttpMessageConverter 在 Jackson 前面,所以不会落到 JSON 转换器——这就是为什么返回纯文本与返回对象走的是不同转换器。
17.8 源码剖析(六):视图解析、异常解析、异步与 Multipart
17.8.1 ViewResolver 链与 UrlBasedViewResolver
DispatcherServlet 的 render 通过 resolveViewNameInternal 顺序遍历 viewResolvers:
private @Nullable View resolveViewNameInternal(String viewName, Locale locale) throws Exception {
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
UrlBasedViewResolver(spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java)是最常用的基类。它的 createView(第 449 行起)先做 prefix/suffix 拼接:视图名 "user/list" 经过 setPrefix("/WEB-INF/views/") + setSuffix(".jsp") 变成 /WEB-INF/views/user/list.jsp,再通过 requiredViewClass() 校验(InternalResourceViewResolver 要求是 InternalResourceView 子类),最后反射实例化 View 并把 URL、contentType 等属性塞进去。
InternalResourceView 的 renderMergedOutputModel 做的事:把 model 里每个 key-value request.setAttribute(key, value),然后 request.getRequestDispatcher(forwardUrl).forward(request, response)。这就是 JSP 视图的完整渲染路径。
ContentNegotiatingViewResolver 是个特殊的「复合」ViewResolver:它不自己渲染,而是根据 Accept 头从所有 ViewResolver 里挑出最合适的 View(甚至可以返回 RequestResponseBodyMethodProcessor 用的那种消息转换视图)。它通常要被配在 ViewResolver 链的最前面(order 最高)。
17.8.2 ExceptionHandlerExceptionResolver:@ExceptionHandler 的解析
ExceptionHandlerExceptionResolver(spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java)维护两个缓存:
exceptionHandlerCache(第 117 行):Map<Class<?>, ExceptionHandlerMethodResolver>——按 controller 类缓存「本类内部@ExceptionHandler方法的查找表」。exceptionHandlerAdviceCache(第 120 行):Map<ControllerAdviceBean, ExceptionHandlerMethodResolver>——按@ControllerAdvicebean 缓存。
doResolveHandlerMethodException 的逻辑:先在当前 handler 所在类的 ExceptionHandlerMethodResolver 里找能处理这个异常类型的方法(ExceptionHandlerMethodResolver 内部维护 Map<Class<?>, Method>,按异常类型继承链精确匹配);找不到再遍历 exceptionHandlerAdviceCache 里所有 @ControllerAdvice,按 @ControllerAdvice 的 basePackages/assignableTypes 等过滤条件判断是否适用于当前 controller。找到后,把该方法包装成 ServletInvocableHandlerMethod,复用与正常 handler 相同的参数解析器与返回值处理器去调用——这就是为什么 @ExceptionHandler 方法也能用 @RequestBody、@PathVariable 这些参数。
17.8.3 ResponseStatusExceptionResolver 与 DefaultHandlerExceptionResolver
ResponseStatusExceptionResolver(spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java)的 doResolveException(节选):
if (ex instanceof ResponseStatusException rse) {
return resolveResponseStatusException(rse, request, response, handler);
}
ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
if (status != null) {
return resolveResponseStatus(status, request, response, handler, ex);
}
它处理两种情况:异常本身是 ResponseStatusException(5.0+),或异常类上标了 @ResponseStatus。最终调 response.sendError(statusCode) 或 sendError(code, reason)(第 150、156 行)——注意 sendError 会触发容器的错误页机制。返回的 ModelAndView 是空的(new ModelAndView()),DispatcherServlet 看到 exMv.isEmpty() 后返回 null,不再渲染。
DefaultHandlerExceptionResolver(spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java)是一张「标准异常 → HTTP 状态码」的硬编码映射表:NoSuchRequestHandlingMethodException→404、HttpRequestMethodNotSupportedException→405(sendError 并设 Allow 头)、HttpMediaTypeNotSupportedException→415、HttpMediaTypeNotAcceptableException→406、MissingServletRequestParameterException→400、MissingPathVariableException→500、NoHandlerFoundException→404、AsyncRequestTimeoutException→503 等。405 的源码答案就在这里:当一个 URL 映射到了某个 controller 但 HTTP method 不在该方法的 consumes/method 列表里时,RequestMappingInfoHandlerMapping 会抛 HttpRequestMethodNotSupportedException,由本 resolver 翻译成 405 并设 Allow 响应头。
17.8.4 WebAsyncManager:三线程异步模型
WebAsyncManager(spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java)的类 javadoc 把模型讲得很清楚(第 43-51 行):
异步场景以一个普通线程(T1)开始请求处理。调用
startCallableProcessing或startDeferredResultProcessing会在另一个线程(T2)产生结果。结果被保存后,请求被分派回容器,在第三个线程(T3)里用保存的结果恢复处理。在 dispatch 线程(T3)里,结果通过getConcurrentResult()访问。
对应 doDispatch 里的几个分叉点:
- T1(容器线程):handler 方法返回
Callable/DeferredResult,由CallableMethodReturnValueHandler/DeferredResultMethodReturnValueHandler调到asyncManager.startCallableProcessing/startDeferredResultProcessing。这些方法在 T2 上执行业务逻辑,完成时把结果存起来。 - T1 的 doDispatch 立即 return:因为
isConcurrentHandlingStarted()返回 true,doDispatch 不渲染、不 afterCompletion,只调applyAfterConcurrentHandlingStarted通知拦截器「异步开始了」。 - T3(容器恢复 dispatch):容器再次把请求 dispatch 进 DispatcherServlet(
DispatcherType.ASYNC)。invokeHandlerMethod里检测到hasConcurrentResult(),把异步结果当成「方法返回值」重新走返回值处理器,完成真正的渲染或消息写回。
超时由 AsyncWebRequest.setTimeout 控制;超时后 WebAsyncManager 会触发 CallableProcessingInterceptor/DeferredResultProcessingInterceptor 的超时回调,最终抛 AsyncRequestTimeoutException 被 DefaultHandlerExceptionResolver 翻译成 503。
17.8.5 StandardServletMultipartResolver
StandardServletMultipartResolver(spring-web/src/main/java/org/springframework/web/multipart/support/StandardServletMultipartResolver.java)非常薄:
@Override
public boolean isMultipart(HttpServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(),
(this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/"));
}
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
@Override
public void cleanupMultipart(MultipartHttpServletRequest request) {
if (!(request instanceof AbstractMultipartHttpServletRequest abstractMultipartHttpServletRequest) ||
abstractMultipartHttpServletRequest.isResolved()) {
try {
for (Part part : request.getParts()) {
part.delete();
}
}
catch (Throwable ex) {
LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex);
}
}
}
要点:
isMultipart只看 Content-Type 是否multipart/开头。strictServletCompliance为 true 时只认multipart/form-data(更严格的 Servlet 规范对齐)。resolveMultipart就是包一个StandardMultipartHttpServletRequest——它内部延迟调用request.getParts()枚举 Servlet 容器解析好的 part,包装成MultipartFile。真正的解析器是 Servlet 容器(Tomcat 等),Spring 只是适配。cleanupMultipart:遍历所有 part 调part.delete()删除临时文件。这就是 doDispatch finally 里调用的那个清理。resolveLazily:默认 false,立即解析(解析失败立即抛 MultipartException);true 则延迟到首次访问 part 时才解析。
17.9 运行机制:全链路时序图与状态流转
把前面逐段拆解的源码串成一张图。下面是一次同步 HTTP 请求(GET /users/1?detail=true,返回 JSON)从容器到响应写回的完整调用栈:
Servlet 容器 (T1)
│
▼
FrameworkServlet.service(request, response)
│ HTTP_SERVLET_METHODS.contains("GET")? ──yes──▶ super.service
│ │
▼ ▼
doGet ────────────────────────────────────────▶ processRequest
│
① LocaleContextHolder.setLocaleContext
② RequestContextHolder.setRequestAttributes
③ asyncManager.registerCallableInterceptor(RequestBindingInterceptor)
│
▼
DispatcherServlet.doService
│
④ attributesSnapshot (include 请求才需要)
⑤ 暴露 WEB_APPLICATION_CONTEXT_ATTRIBUTE / LOCALE_RESOLVER_ATTRIBUTE
⑥ flashMapManager.retrieveAndUpdate
⑦ ServletRequestPathUtils.parseAndCache (parseRequestPath=true 时)
│
▼
DispatcherServlet.doDispatch
│
┌─────────────────────────────────┼──────────────────────────────────┐
▼ ▼ ▼
checkMultipart(request) getHandler(request) applyPreHandle
multipartResolver.isMultipart? 遍历 handlerMappings: for interceptor:
yes → resolveMultipart() RequestMappingHandlerMapping interceptor.preHandle
no → 原 request .getHandlerInternal false → triggerAfterCompletion
返回 HandlerExecutionChain; → return
(装配 adaptedInterceptors / true → interceptorIndex=i
MappedInterceptor / CORS)
│
▼
getHandlerAdapter(handler)
遍历 handlerAdapters, supports(handler)=true
→ RequestMappingHandlerAdapter
│
▼
ha.handle(...)
│
▼
RequestMappingHandlerAdapter.handleInternal
│
▼
invokeHandlerMethod
│
├─ modelFactory.initModel (跑 @ModelAttribute 方法)
├─ invocableMethod.invokeAndHandle(webRequest, mavContainer)
│ │
│ ▼
│ InvocableHandlerMethod.invokeForRequest
│ │
│ ├─ getMethodArgumentValues
│ │ for 参数 i:
│ │ Composite.getArgumentResolver (带缓存)
│ │ → RequestParamMethodArgumentResolver 等
│ │ → resolver.resolveArgument(...)
│ │
│ └─ doInvoke → method.invoke(controller, args)
│ (拆包 InvocationTargetException)
│
└─ returnValueHandlers.handleReturnValue
│
▼
RequestResponseBodyMethodProcessor.handleReturnValue
│
▼
writeWithMessageConverters
│
acceptableTypes(Accept) × producibleTypes(转换器能写的)
│
selectedMediaType = application/json
│
遍历 messageConverters:
MappingJackson2HttpMessageConverter.canWrite=true
│
beforeBodyWrite 钩子
│
converter.write → Jackson 序列化为 JSON → response
mavContainer.setRequestHandled(true)
│
▼
getModelAndView(mavContainer, ...) → null (requestHandled)
│
▼
applyDefaultViewName(mv==null, 跳过)
applyPostHandle (逆序 postHandle)
│
▼
processDispatchResult
│
├─ exception==null?
│ mv==null (requestHandled) → 不 render
│ mappedHandler.triggerAfterCompletion(逆序, 从 interceptorIndex)
▼
(doDispatch finally)
└─ multipartRequestParsed? cleanupMultipart
│
▼
processRequest finally
├─ resetContextHolders (还原 previous*)
├─ requestAttributes.requestCompleted()
└─ publishRequestHandledEvent
│
▼
response 返回容器
17.9.1 状态流转的关键状态点
整条链路上有几个「隐式状态」决定了下一步走向,理解它们比记方法名更重要:
multipartRequestParsed(doDispatch 局部布尔):决定 finally 是否 cleanup。它不影响 handler 选择,只影响资源释放。interceptorIndex(HandlerExecutionChain 字段):决定 afterCompletion 回调范围。preHandle 每成功一个 +1;短路时停在最后一个成功的位置。mavContainer.requestHandled:决定是否走视图渲染。@ResponseBody、@ResponseStatus、304、返回 null 等都会把它设为 true;为 true 时 doDispatch 的processDispatchResult看到 mv==null 直接跳过 render。asyncManager.isConcurrentHandlingStarted():决定是否跳过 postHandle/render/afterCompletion。异步一旦启动,T1 的收尾全部简化为「通知拦截器 afterConcurrentHandlingStarted」。dispatchException(doDispatch 局部):非 null 时进入processHandlerException,走 resolver 链。resolver 链全空才把异常重新抛出。
17.9.2 为什么这样设计:变化点分析
整条链路本质上是「用适配器模式 + 责任链模式」把四个变化点隔离开:
| 变化点 | 谁负责 | 扩展方式 |
|---|---|---|
| URL → handler 的映射规则 | HandlerMapping | 自定义 HandlerMapping / 实现 WebMvcConfigurer 加拦截器 |
| handler 形态(Controller 接口 / HttpRequestHandler / 注解方法 / RouterFunction) | HandlerAdapter | 一般不需要自定义,注解场景只用 RequestMappingHandlerAdapter |
| 参数从请求里怎么取 | HandlerMethodArgumentResolver | 实现 WebMvcConfigurer.addArgumentResolvers |
| 返回值怎么写出去 | HandlerMethodReturnValueHandler + HttpMessageConverter | 自定义返回值处理器 / 注册新的 HttpMessageConverter |
| 异常怎么变成响应 | HandlerExceptionResolver | @ControllerAdvice / 自定义 resolver |
| 视图名怎么变成页面 | ViewResolver | 自定义 ViewResolver |
DispatcherServlet 本身对这些变化点一无所知,它只按固定顺序调用接口方法。这就是为什么 SpringMVC 能在不改 DispatcherServlet 一行代码的前提下,从 JSP 时代演进到 REST JSON、再到函数式路由、再到异步响应式。
17.9.3 线程上下文的生命周期
RequestContextHolder/LocaleContextHolder 是 ThreadLocal。整条链对它的管理有一条铁律:谁绑定,谁还原。FrameworkServlet.processRequest 在开头绑定、在 finally 还原;异步场景下 RequestBindingInterceptor 保证 T2(Callable 执行线程)在执行业务前后也绑定/还原。因此:
- 同步业务代码里任何一层都能
RequestContextHolder.getRequestAttributes()拿到当前请求。 - 但如果你在业务代码里
new Thread()自己起线程,那个线程里没有绑定——必须显式包装或用 Spring 的TaskDecorator传递。 - 请求结束后 ThreadLocal 被还原成 previous 值,线程归还容器线程池,下一个请求拿到的是干净上下文。
17.10 本章小结
本章从 Servlet 继承链讲起,一路拆到:
- HttpServletBean 用 BeanWrapper 把 init-param 变成 bean 属性,再调模板方法
initServletBean。 - FrameworkServlet.processRequest 负责请求级 ThreadLocal 的绑定与还原,所有 HTTP method 统一收口到
doService。 - DispatcherServlet.initStrategies 装配九大策略,默认值来自
DispatcherServlet.properties。 - doDispatch 是主流水线:checkMultipart → getHandler → applyPreHandle → getHandlerAdapter → ha.handle → applyDefaultViewName → applyPostHandle → processDispatchResult(异常解析 + render)→ afterCompletion。
- HandlerMapping 体系:
AbstractHandlerMapping.getHandler是 final 模板,子类实现getHandlerInternal;RequestMappingHandlerMapping用isHandler扫@Controller,7.0.9 默认 PathPatternParser。 - 参数解析:
RequestMappingHandlerAdapter装配 27 个内置参数解析器(注解组 + 类型组 + catch-all),InvocableHandlerMethod.getMethodArgumentValues逐个解析,Composite 带缓存。 - 返回值处理:
ServletInvocableHandlerMethod.invokeAndHandle决定是否 requestHandled;@ResponseBody走RequestResponseBodyMethodProcessor→writeWithMessageConverters做媒体类型协商,再顺序遍历HttpMessageConverter选第一个 canWrite 的。