☰
Spring MVC请求流程源码解析:从DispatcherServlet到视图响应的全链路
2026/10/2 16:12:34 网站建设 项目流程

后端面试里“Spring MVC请求流程”算是被问得最频繁的一道题,但多数人只能背出 DispatcherServlet → HandlerMapping → HandlerAdapter 这种三行答案。说实话,只背到这一步,面试官一追问细节就露馅,线上遇到问题也不知道该从哪里断点。我自己的习惯是:遇到请求相关的问题,直接打开源码,从doDispatch()方法一行一行往下看,把一次请求从头到尾走完。这篇文章就从源码视角把整条链路拆开,再把这几年实战里踩过的坑一并整理出来。

先说一下这篇文章适合谁:刚看完Spring基础、准备啃Spring MVC源码的初学者,能顺着链路把核心类串起来;有两年左右经验的后端,遇到参数解析、视图解析、异常处理这类问题能少走弯路。读完你会清楚一个HTTP请求从进入Servlet容器开始,到返回响应给客户端,中间到底经过哪些核心组件、每个组件在做什么、为什么有的接口返回JSON,有的接口却返回了视图名。

1. 一次请求的完整链路:先看清全局地图

1.1 请求到达DispatcherServlet前,容器做了什么

很多人一说Spring MVC就只盯着DispatcherServlet,但请求真正到达它之前,Servlet容器(Tomcat、Jetty等)已经做了一轮工作。以Tomcat为例,客户端发来一个HTTP请求,Tomcat的连接器线程会解析请求行、请求头、请求体,然后根据url-pattern或@WebServlet的映射规则,找到对应的Servlet来接管。这个阶段发生的事跟Spring MVC还没有任何关系,Spring MVC只是在这个环节被选中后才开始介入。

DispatcherServlet本身就是一个Servlet,它继承了HttpServlet,你在配置里看到的DispatcherServlet或者Spring Boot自动配置里注册的那个dispatcherServlet,最终都会被映射到一个路径上。常见的配置方式有两种:一种是在web.xml里写<servlet-mapping>,把/交给DispatcherServlet;另一种是Spring Boot中通过ServletRegistrationBean注册。这里有个容易忽略的细节:映射路径是/还是/*,行为差异非常大。/只匹配不含后缀的路径,静态资源、JSP等不会被拦截;/*则匹配所有路径,包括JSP和静态资源,这个在后面的避坑章节再展开。

请求进入DispatcherServlet后,很多人的源码阅读之旅就是从这里开始的。FrameworkServlet里有一个service()方法,它会调用doGet()、doPost()等,这些方法最终都汇聚到doService(),然后调用核心的doDispatch()。换句话说,整个Spring MVC的请求处理中枢就是DispatcherServlet.doService()和doDispatch(),一个是环境准备,一个是核心分发。

1.2 DispatcherServlet.doDispatch():整个流程的总纲

打开DispatcherServlet的源码,doDispatch()方法并不长,三四分钟就能逐行读完。它的逻辑可以抽取成一句话:把请求交给合适的处理器去处理,然后把处理结果包装成响应返回给客户端。这句话背后藏着组件协作的完整链条。

DispatcherServlet在初始化时(initStrategies)会从Spring容器里查找一组策略组件,关键的有这几类:

  • HandlerMapping:负责根据请求找到处理器(Handler)和拦截器(Interceptor),结果封装在HandlerExecutionChain里;
  • HandlerAdapter:负责真正调用处理器方法,适配不同的调用方式;
  • HandlerExceptionResolver:处理请求过程中抛出的异常,转换成响应;
  • ViewResolver:当处理器返回逻辑视图名时,负责解析成真正的视图;
  • LocaleResolver、ThemeResolver、MultipartResolver等:分别处理国际化、主题和文件上传解析。

在doDispatch()里,顺序大致是这样的:先处理Multipart请求,把请求包装成MultipartHttpServletRequest;然后遍历HandlerMapping,拿到HandlerExecutionChain;接着遍历HandlerAdapter,找到能支持该Handler的适配器;执行拦截器的preHandle;调用处理器方法,得到ModelAndView;执行拦截器的postHandle;最后交给processDispatchResult处理视图渲染或异常。

这里我建议初学者别急着背代码,先在IDEA里给doDispatch()打上断点,然后用浏览器或Postman发一个最简单的请求。你会看到流程非常清晰,每个变量的变化都能在调试窗口里看到。这种方式比任何资料都直观。真正把doDispatch()吃透了,后面的源码量再大也都是在这个框架里填肉。

2. HandlerMapping:怎么找到能处理请求的那个“处理器”

2.1 三种HandlerMapping的定位逻辑

先看HandlerMapping接口,核心方法只有一个:getHandler(HttpServletRequest request)。它返回HandlerExecutionChain,里面装着一个Handler和若干拦截器。Spring MVC默认注册了多个HandlerMapping,它们按顺序匹配,谁先命中谁负责。

默认配置里比较重要的几个:RequestMappingHandlerMapping,这是最核心的一个,它通过@RequestMapping注解建立请求条件和处理器方法之间的映射关系,负责我们平时写的Controller方法;SimpleUrlHandlerMapping,它通过配置URL与Handler的映射来匹配,适合传统的XML配置方式;BeanNameUrlHandlerMapping,它把Bean名称当作URL模式来匹配。在现代项目中,绝大多数请求都由RequestMappingHandlerMapping处理,另外两个更多用于特殊场景,比如静态资源的ResourceHttpRequestHandler处理。

RequestMappingHandlerMapping的底层是个MappingRegistry,它保存了PathPattern(或AntPath)到HandlerMethod的映射。当一个请求进来,它提取请求的路径、HTTP方法、请求参数、请求头等条件,逐一比对注册表里的RequestMappingInfo。比如一个@GetMapping("/user/{id}"),它记录的请求条件就是路径/user/{id}、方法限定GET、参数无要求、Header无要求。Spring Boot 2.6以上默认启用了新的PathPatternParser,它与旧版AntPathMatcher在路径匹配规则上有些差异,比如/**只匹配多层路径,不再匹配单层路径。

由于存在多个HandlerMapping按Order排序,查找时会按顺序尝试,一旦某个HandlerMapping返回了非空的HandlerExecutionChain就立即停止。有时候你注册了自定义的HandlerMapping,没有注意它的Order值,导致它抢在RequestMappingHandlerMapping前面处理了本该由注解方式匹配的请求,就会出现“请求没走进Controller”的诡异现象。

2.2 查找过程中容易踩的坑

第一个常见坑是静态资源404。把请求路径映射到/后,DispatcherServlet其实并不认识css/js/png这类静态资源。它的HandlerMapping根本无法在MappingRegistry里找到对应处理器,于是返回404。解决办法通常是两步:在Spring Boot里默认已经配置了ResourceHttpRequestHandler来处理静态资源,路径默认是/**对应classpath下的static目录;如果手写配置,可以把ResourceHandlerRegistry的相关配置补上。另一个办法是配置<mvc:default-servlet-handler/>,把处理不了的请求交回给Servlet容器默认的Servlet去处理。这两种思路本质上是“给Spring MVC提供一个兜底处理器”。

第二个坑是两个Controller的方法有重复的请求映射路径。比如@GetMapping("/user/{id}")写了两遍,Spring容器在启动时会检测到路径冲突并抛异常。但如果一个是/user/{id},另一个是/user/{name},在路径变量占据相同位置的情况下,运行时才会出现Ambiguous mapping的异常。这个报错信息会明确告诉你哪两个方法冲突,定位起来很快。

第三个关于拦截器。HandlerExecutionChain是Handler和拦截器的组合,Spring MVC的拦截器并不是Servlet规范的Filter,而是Spring自己的HandlerInterceptor。它的执行顺序是preHandle→ 处理器方法 →postHandle→afterCompletion,前一个拦截器preHandle返回false,后续拦截器和处理器方法都不会执行。很多人在拦截器里做一些权限校验,但忘记对静态资源放行,最后页面白屏,卡在资源加载上。遇到这种情况,第一反应应该去看拦截器的excludePathPatterns是否配置完整。

3. HandlerAdapter与InvokableHandlerMethod:真正调用我们写的代码

3.1 适配器模式选型与核心结构

HandlerAdapter是整个调用链里最关键的一环。它存在的意义在于:DispatcherServlet不关心具体的Handler长什么样,它只要求每个处理器都能被某个适配器调用。适配器模式让Spring MVC既支持注解方式的HandlerMethod,也支持Controller接口实现和其他类型的Handler。

RequestMappingHandlerAdapter是最常用的一类适配器,它专门支持@RequestMapping注解标注的方法。它内部把Handler方法包装成ServletInvocableHandlerMethod,这个类负责在调用目标方法前处理参数解析,在调用后处理返回值。深入看,ServletInvocableHandlerMethod继承了InvocableHandlerMethod,什么东西放在父类、什么东西放在子类,是理解这套设计的关键:InvocableHandlerMethod只负责“参数解析+反射调用”,而ServletInvocableHandlerMethod额外多了“返回值处理”和响应状态的设置。

我建议读者把InvocableHandlerMethod当成核心研究对象,尤其是它的getMethodArgumentValues()方法和doInvoke()方法。前者决定了你写在Controller方法里的参数是怎么从HTTP请求里拿到值的,后者决定了方法到底怎么被反射调用。其中参数解析是整个流程里最容易出问题、也最能体现源码功底的部分。

3.2 参数解析器逐个过关

Spring MVC把“怎么从请求里取参数”这个职责交给了HandlerMethodArgumentResolver接口,它只有两个方法:supportsParameter和resolveArgument。调用时,InvocableHandlerMethod会遍历所有已注册的参数解析器,找到第一个supportsParameter返回true的解析器,然后用它去解析参数。

默认注册的解析器非常多,从名字就能看出用途:

  • RequestParamMethodArgumentResolver:处理@RequestParam注解,把请求参数绑定到方法参数;
  • PathVariableMethodArgumentResolver:处理@PathVariable注解,从URL模板变量取值;
  • RequestHeaderMethodArgumentResolver:处理@RequestHeader注解,从请求头取值;
  • RequestBodyMethodArgumentResolver:处理@RequestBody注解,通过HttpMessageConverter把请求体反序列化成Java对象;
  • ModelAttributeMethodProcessor:处理@ModelAttribute注解,也处理没有注解的复杂对象参数,支持表单绑定的情况。

这里最容易出错的场景有两个。第一个是@RequestBody和@RequestParam混用导致415。当你用@RequestBody接收JSON时,客户端请求的Content-Type必须是application/json,否则Spring MVC会直接返回415 Unsupported Media Type。很多新手前端没设这个请求头,后端口口声声说接口有问题。第二个是参数类型转换失败,比如前端传了"123"而接口定义Integer id,Spring MVC会用ConversionService做类型转换,一旦转换失败,默认抛出MethodArgumentTypeMismatchException,最后返回400。这类问题排查起来非常快,因为异常信息里会带“Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'”这样的明确描述。

还要注意@RequestParam(required = false)和没有注解的小差别。如果你给一个参数加了@RequestParam但没设置required=false,那这个参数默认必传,缺了直接400。而如果不加任何注解,Spring MVC先把参数当成简单类型处理,如果类型不匹配再尝试把整个请求体绑定到复杂对象上。很多老项目里出现“请求字段丢失但接口没报错”的情况,就是因为参数解析器选错了,比如本应取@RequestBody里的字段,结果参数解析器从query string里取,取不到就给了null。

3.3 返回值处理器与@ResponseBody的JSON输出

处理器方法执行完后,ServletInvocableHandlerMethod要决定怎么处理返回值。这一步交给HandlerMethodReturnValueHandler,接口方法也是两个:supportsReturnType和handleReturnValue。返回值处理器同样有一条责任链,常见的处理器有以下几类:

  • ModelAndViewMethodReturnValueHandler:处理方法返回ModelAndView的情况;
  • RequestResponseBodyMethodProcessor:处理标注了@ResponseBody的方法,直接序列化对象写入响应体;
  • HttpEntityMethodProcessor:处理返回ResponseEntity/HttpEntity的情况,也会直接写响应体;
  • ViewNameMethodReturnValueHandler:处理返回void、且没有写入响应体的情况,把方法里通过Model添加的数据封装成ModelAndView,视图名默认为请求路径;
  • StreamingResponseBodyReturnValueHandler:处理流式输出的情况,比如下载大文件。

重点说一下RequestResponseBodyMethodProcessor。它先判断当前Handler方法的返回值类型上是否有@ResponseBody注解(类级别或方法级别),有就交给它处理。它内部会遍历HttpMessageConverter,找到一个能写目标返回类型的转换器,比如在Spring Boot Web场景下MappingJackson2HttpMessageConverter可以把一个User对象序列化成JSON。写响应体之前,它还会做几件事:设置响应状态码(如果Controller方法用了@ResponseStatus)、设置响应头(如果有Content-Type需求)、把返回对象写入HttpServletResponse的输出流。这个类内部的writeWithMessageConverters方法就是JSON输出最底层的实现,打断点跟一遍,比背十篇博客都理解得深。

反过来说,如果你在Controller方法上忘了加@ResponseBody,返回值是一个普通的User对象,Spring MVC会认为这是一个需要找视图来渲染的对象,于是交给其他返回值处理器,最后得到一个“逻辑视图名”,然后由ViewResolver去解析。这也是为什么很多新手有“返回JSON却跳了个页面”的困惑:本质是返回值处理选错了处理器。

4. 视图解析与响应返回:到底什么时候用ViewResolver

4.1 ModelAndViewContainer的状态判断

经过参数解析和方法调用之后,返回结果最终被封装到ModelAndViewContainer里。这个容器对象有两个标志位特别关键:一个是requestHandled,它表示“请求是否已经被完全处理,不再需要视图渲染”;另一个是view字段,它保存要渲染的视图名或视图对象。

当返回值处理器执行完,ModelAndViewContainer的状态决定了后续流程走向。比如RequestResponseBodyMethodProcessor在写完JSON后会让requestHandled = true,因为响应体已经写出,不需要视图解析了。而此时ModelAndViewContainer里的view字段往往还是null。反过来,一个返回逻辑视图名的方法(比如返回字符串"user/list"),它的requestHandled仍为false,后续processDispatchResult就会尝试用ViewResolver解析视图来渲染。

processDispatchResult是DispatcherServlet里负责“收尾”的方法:如果mv不为null且view还没被渲染,它就从ViewResolver列表里找能解析的Resolver,解析成View对象,再调用view.render()把模型数据渲染进最终页面。这就是整个MVC里“View”的部分。

4.2 返回JSON与返回页面混合场景的处理

同一个Controller里通常既有返回JSON的接口,也有返回页面的方法。在前后端分离不太彻底的传统项目里,这个混合场景很常见。以一个典型的Controller为例:

@Controller @RequestMapping("/user") public class UserController { @GetMapping("/detail") @ResponseBody public User detail(@RequestParam Long id) { return userService.getById(id); } @GetMapping("/list") public String list(Model model) { model.addAttribute("users", userService.listAll()); return "user/list"; } }

第一个方法返回JSON,它通过@ResponseBody走完了请求体写入;第二个方法返回视图名,Spring MVC把它交给InternalResourceViewResolver,拼接前缀/WEB-INF/views/和后缀.jsp,得到/WEB-INF/views/user/list.jsp,然后转发渲染。

从结果看,一个是纯数据,一个是HTML页面,两者互不干扰,但底层走的流程完全不同。这提醒我们一个关键点:返回类型的语义是由返回值处理器决定的,不是由方法签名决定的。同样是返回String,加了@ResponseBody就是JSON字符串,不加就是逻辑视图名。想明白这一点,很多让人挠头的现象就都能解释了。

4.3 RestController与Controller对视图解析的差别

@RestController的源码其实就是@Controller和@ResponseBody的组合注解。它等价于类上标注了@Controller,并且该类的每个处理方法默认带上了@ResponseBody。从视图解析的角度看,使用@RestController的类,所有方法返回值都会被当成直接写响应体来处理,不存在视图解析环节。

这个设计在后端接口开发里非常实用。假设一个项目里接口全部返回JSON,那么用@RestController是最干净的写法。但如果某个类是个页面Controller,返回视图名,用@Controller是合适的。一旦混用,比如用@RestController写了一个返回页面的方法,最后前端收到的会是“user/list”这个字符串本身,而不是HTML页面。这个问题我在接手旧项目时见过不止一次,通常是前人从某个Controller复制代码,顺手改了注解但没注意到方法的用途。

还要提一下ResponseEntity。它是HttpEntity的子类,可以在返回结果的同时设置状态码和响应头。当处理器返回ResponseEntity时,HttpEntityMethodProcessor会把它当作请求已处理完,写入响应体。所以哪怕@RestController类下有个方法返回ResponseEntity<String>,也不会走视图解析。用ResponseEntity的好处是灵活控制HTTP状态码,适合RESTful API的设计。

5. 异常处理与兜底逻辑

5.1 HandlerExceptionResolver责任链

请求处理过程中抛出的异常并不会直接变成500错误,Spring MVC会把它送到HandlerExceptionResolver责任链里处理。DispatcherServlet默认注册了三个重要的异常处理器:

  • ExceptionHandlerExceptionResolver:扫描@ExceptionHandler注解方法,支持在@ControllerAdvice里集中定义异常处理逻辑;
  • ResponseStatusExceptionResolver:处理@ResponseStatus注解标注的异常,直接设置响应状态码;
  • DefaultHandlerExceptionResolver:处理Spring MVC自身抛出的标准异常,比如NoHandlerFoundException、MethodArgumentNotValidException等,转为对应的HTTP状态码。

当处理器方法抛出异常时,doDispatch()会捕获异常,调用processHandlerException,把这个异常交给这几个处理器依次尝试。这里的关键设计是:异常处理器返回的ModelAndView可以是空视图(表示异常已处理,直接渲染),也可以是包含视图名的ModelAndView(表示要渲染一个错误页面),还可以返回null,表示“我处理不了”,让容器继续抛出异常。

ExceptionHandlerExceptionResolver内部的工作方式和RequestMappingHandlerAdapter非常像:它也需要做参数解析和返回值处理,只不过目标方法是@ExceptionHandler标注的异常处理方法。这解释了一个坑:你在@ExceptionHandler方法里返回了一个String,如果没有加@ResponseBody,它会被当成逻辑视图名,页面上显示的是HTTP 500加一堆异常栈,而不是你想要的JSON。所以全局异常处理器返回JSON时,要么在方法上加@ResponseBody,要么把异常类所在类标注为@RestControllerAdvice。

5.2 异常处理常见坑

第一个坑是异常被“半路拦截”。如果在Spring MVC的HandlerInterceptor的preHandle里抛了异常,HandlerExceptionResolver不一定能处理它。因为它可能在真正进入doDispatch()的处理器调用阶段之前就抛出了,如果主流程没有进入processHandlerException分支,异常就顺着容器栈继续往外冒。在实际项目里,权限拦截器抛异常时如果没经过@ExceptionHandler,最后看到的往往是一个Tomcat默认错误页。这时候如果你确实想让全局异常机制兜底,可以在拦截器里 try-catch 或者把异常包装后在afterCompletion阶段处理。

第二个坑是404没有日志。大部分时候404意味着没有对应的HandlerMapping命中,这时处理器调用根本不会发生,当然也没有自定义异常处理器介入的余地。线上排查404时,第一件事是确认请求URL有没有被某个HandlerMapping命中,而不是去翻用户的异常日志。Spring Boot里可以通过server.error.include-message=always来让404响应带上具体信息,辅助定位。

第三个坑是@ControllerAdvice与局部@ExceptionHandler的优先级问题。Spring的规则是:局部异常处理方法(在当前Controller内)优先于全局的@ControllerAdvice。如果你在某个Controller里写了一个范围很大的异常处理方法(比如捕获Exception),它会把全局的异常处理给盖掉。这本身是设计好的行为,但很容易让人困惑:明明全局异常处理器写了,为什么某些接口的异常没有按全局方式返回?顺着优先级检查一遍就能定位。

6. 高频问题速查与线上排查思路

6.1 高频问题速查表

把过去的经验和群里问得最多的问题汇总一下,直接整理了张速查表:

现象可能原因排查方向
请求没进Controller,返回404HandlerMapping没有匹配;静态资源被拦;NoHandlerFoundException未启用看URL是否是静态资源路径,看日志是否有“No mapping for POST”
POST请求用@RequestParam取不到值请求体格式不是application/x-www-form-urlencoded;前端传的是JSON却没用@RequestBody打开浏览器F12看Content-Type和请求体格式
@RequestBody返回415请求头Content-Type不是application/json;缺少JSON转换依赖检查请求头、检查pom中是否有jackson-databind
返回JSON却出现了视图名或跳转错误页面方法上没加@ResponseBody;类上用了@Controller而不是@RestController检查返回值处理器选的哪个,依据是方法有没有@ResponseBody
拦截器没生效拦截器注册的路径范围不对;preHandle返回了false检查WebMvcConfigurer.addInterceptors的路径配置
异常没有被全局处理异常发生在拦截器阶段;局部@ExceptionHandler覆盖了全局;异常处理器返回了null打断点看processHandlerException是否被调用
前端提交表单后跳回同一页面且是GET表单POST的方法没写@PostMapping,Spring MVC按GET处理并返回视图检查URL是否匹配GET方法,反馈的状态码是405

这张表覆盖了我在群里答疑时超过八成的问题。大多数问题的根源,都是对“参数解析器选型”和“返回值处理器选型”理解不到位。这两条责任链是Spring MVC请求流程里的灵魂,建议花时间跟源码。

6.2 线上排查的经验

说两个亲测有效的排查思路。

第一个思路是直接在DispatcherServlet.doDispatch()方法入口打断点,然后把HandlerMappings、HandlerAdapters的局部变量列表展开看。比如怀疑某个请求被错误的HandlerMapping处理了,展开mappedHandler就能看到HandlerExecutionChain里到底包着哪个HandlerMethod。如果发现HandlerMethod是自己那个Controller方法,说明HandlerMapping没问题;如果HandlerMethod是个ResourceHttpRequestHandler,说明请求被当成静态资源处理了,这时候根本不是Controller问题,而是路径映射问题。

第二个思路是看异常处理链路的走向。在processHandlerException方法里打断点,观察exception变量和处理结果ModelAndView。如果exception里是NoHandlerFoundException,说明URL从根上就没匹配上任何路由;如果异常处理器最终返回的mav为null,异常会继续抛出到容器,这种情况下自定义的全局异常处理器没有生效,需要往拦截器阶段排查。

第三个建议是开启日志观察“视图解析”阶段。在application.properties中设置logging.level.org.springframework.web.servlet=DEBUG,请求结束后日志会打印Completed 200 OK这样的记录。如果某个请求经历过视图解析,日志里会显示View name 'xxx', model {....},而REST接口通常不会出现这方面的日志。这个差别在排查混合项目问题时特别有用。

还有个小技巧:在Spring Boot中想看当前项目到底注册了哪些HandlerMapping,可以在启动后注入ApplicationContext,用getBeansOfType(HandlerMapping.class)打印出所有HandlerMapping的类和Order。这样可以快速确认自定义HandlerMapping是否和默认组件冲突,也能看出ResourceHandlerMapping是否被正确注册。

6.3 从源码角度给出的避坑清单

  • 写任何Controller方法前先想清楚它属于“返回数据”还是“返回视图”,据此决定用@RestController还是@Controller,决定要不要加@ResponseBody。

  • 前后端分离项目里,建议全局接口层统一使用@RestController,不要在一个类里混用页面和接口。如果确实要混用,方法上的注解一定要写清楚,避免从@RestController复制过来的代码忘记删掉@ResponseBody。

  • 配置WebMvcConfigurer时,静态资源的放行要放在拦截器addInterceptors之前理清思路,先想明白哪些路径需要被Spring MVC接管,哪些需要交给ResourceHttpRequestHandler。

  • 接口参数接收JSON时,优先用DTO对象 +@RequestBody,不要用Map<String, Object>,这样参数校验、类型转换、字段命名都能更可控。启动时开启spring.jackson相关配置,确认JSON序列化字段命名规则不会和前端不一致。

  • 全局异常处理器建议单独新建一个类,用@RestControllerAdvice,方法返回统一的结构体(比如Result<T>),彻底避免异常处理方法又把String当视图名返回的坑。

  • 不要随便在Controller里直接用HttpServletResponse写数据。一旦你手动写了响应体,requestHandled会被标记为true,后续再返回一个对象,Spring MVC就不会再做视图解析了,这个行为依赖性强,容易因返回值解析器的变化出现意外。

  • 如果某个请求迟迟不返回,优先看是不是视图解析没有完成(比如视图名拼错导致跳到一个不存在的页面),而不是先怀疑数据库慢查询。Spring MVC的视图解析阶段确实可能吞掉一部分时间。

尾声

聊到最后,倒不是说要把所有源码都背得滚瓜烂熟才叫“吃透”。那个范围太大了,也没有必要。真正有收获的做法是带着问题去读:遇到一个异常,先看一眼doDispatch()走到哪一步出的问题;再顺着参数解析器和返回值处理器的责任链去定位是选型错误,还是数据格式不对。这套流程就像一张地图,地图上的每个节点是干嘛的、什么时候会被走到,看多了自然就熟了。我自己刚学的时候是用断点一遍一遍断出来的,有时候一个问题要断四五遍,但每断一遍,对Spring MVC的理解就扎实一分。别怕慢,这类基础框架的源码,慢就是快。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询