
图中映射器负责找处理目标,适配器负责调用,返回结果再按语义写响应或解析视图。
一个请求到了 Controller 方法之前,究竟经过哪些步骤?如果只背 DispatcherServlet、HandlerMapping、HandlerAdapter、ViewResolver,容易误以为所有返回值最后都要渲染页面。先限定这里讨论 Servlet 栈下 Spring MVC 的常规同步处理主线,再逐个说明这些组件负责的决定。
前端控制器不亲自包办细节
请求先由 Servlet 容器按映射规则送到 DispatcherServlet。它提供统一的调度过程,把寻找处理目标、调用目标、处理异常和生成响应委托给相应组件。入口集中,不代表一个类需要理解所有 Controller 的调用细节。
容器过滤器还可能在到达它之前处理请求。过滤器与 Spring MVC 拦截器的层次不同,不能把所有请求前处理都称为 HandlerInterceptor。请求尚未进入控制器,也不代表一定已经经过每一个 MVC 组件。
如果分析的是异步处理、流式输出或其他特殊路径,执行线程与回调顺序可能改变。先把常规同步主线说清,再补特殊情况,比承诺“从进到出永远一条线程”更可靠。
映射器回答谁来处理
HandlerMapping 根据请求路径、HTTP 方法以及相应的匹配条件寻找处理器。注解式 Controller 常见的是对应到某个处理方法,找到的结果还可能包含适用的拦截器链。
这一步不是直接执行业务。没有匹配处理目标、请求方法不符合等情况,可能在到达业务方法之前形成相应结果或异常。不能把所有非成功响应都归因于 Controller 自己返回错误。
拦截器也可能在调用前终止后续处理,例如已经完成响应。具体回调顺序取决于哪些前置回调实际成功执行及是否出现异常,不宜背一个没有条件的“所有拦截器总会完整跑三次”。

适配器回答怎样调用
DispatcherServlet 拿到处理器后,借助合适的 HandlerAdapter 执行它。对注解处理方法,适配过程会涉及参数解析、类型转换、数据绑定和相应验证,再调用目标方法,随后处理返回值。
例如路径中的编号、查询参数和请求体不是同一种输入来源。请求体转换还受媒体类型和可用消息转换器影响。把字符串参数变成数字失败,或者请求体格式不符合要求,都可能让调用在真正进入业务逻辑前停止。
所以,调试一个方法“没有进去”时,应先看映射是否成功,再看参数解析和转换的错误,而不是只在方法第一行反复加日志。定位顺序应跟组件责任对应。
返回字符串未必是同一种含义
普通视图式控制器返回一个字符串,常用来表示逻辑视图名,再交给 ViewResolver 找到实际 View,结合模型数据渲染响应。这是页面路径,不是所有控制器都必须经过的最后一关。
如果返回值由 @ResponseBody 语义处理,或使用带相应语义的控制器,结果可以通过 HttpMessageConverter 写入响应体。返回对象时常见的是相应数据格式转换,但具体格式取决于协商与转换器;返回字符串也不必一律先包装成 JSON。
ResponseEntity 还能表达状态和头信息。知道方法返回了某种 Java 类型,不足以决定最终 HTTP 结果,还要看注解、返回值处理器与响应配置。不要把 REST 路径强行画成必然先构造 ModelAndView 再渲染模板。
异常也有自己的处理链
请求处理期间发生的异常,可以交给相应的异常解析器处理,映射成错误响应、错误视图或其他结果。具体哪些异常怎样呈现,受控制器建议、框架配置和响应状态等因素影响,不是每次抛异常都固定变成同一个页面。
若响应已经部分写出,后续再出错,也不一定能够无代价地重新生成一个完整错误体。业务异常与传输阶段异常需要区分,日志里的堆栈不能自动证明客户端究竟收到了什么。
用责任链来组织排查
可以沿着入口映射、处理目标、调用适配、参数解析、业务执行、返回值处理、响应生成这条链检查,并单独看异常和拦截。每一步问“输入是什么、决定了什么、结果交给谁”,比只按类名顺序背诵更有解释力。
如果同样返回字符串,一个方法进入模板视图,另一个却直接把文字写给客户端,你会先检查返回类型,还是检查控制器与方法上的响应语义?