尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

一个请求进来到底发生了什么?Spring MVC 请求处理全链路源码拆解

一个请求进来到底发生了什么?Spring MVC 请求处理全链路源码拆解 一个请求进来到底发生了什么Spring MVC 请求处理全链路源码拆解从浏览器点下按钮到 Controller 方法执行中间经历了多少环节为什么 RequestBody 能自动把 JSON 变成对象为什么返回值自动变成 JSON为什么 404 长那样这一篇把 DispatcherServlet 从请求进入到响应返回的完整链路拆给你看。一、Spring MVC 的核心DispatcherServlet所有请求的总入口是DispatcherServlet。它就是一个特殊的 Servlet拦截所有匹配的 URL然后统一调度。用户请求 │ ▼ ┌──────────────────────────────────────────────────────────────┐ │ DispatcherServlet │ │ │ │ 1. HandlerMapping ──► 找到处理该请求的 Controller 方法 │ │ 2. HandlerAdapter ──► 适配执行 Controller 方法 │ │ 3. 参数解析器 ──► 把请求参数变成方法入参 │ │ 4. Controller 方法 ──► 执行业务逻辑 │ │ 5. 返回值处理器 ──► 把返回值变成响应数据 │ │ 6. 消息转换器 ──► 把数据序列化成 JSON/XML │ │ 7. HandlerException ──► 异常统一处理 │ │ 8. ViewResolver ──► 返回视图时解析视图 │ └──────────────────────────────────────────────────────────────┘ │ ▼ 用户响应这张图里的 8 个环节就是 Spring MVC 的全部家当。下面逐个拆。二、DispatcherServlet 的初始化9 大组件DispatcherServlet在启动时通过initStrategies()初始化 9 大组件组件作用默认实现MultipartResolver文件上传解析StandardServletMultipartResolverLocaleResolver国际化AcceptHeaderLocaleResolverThemeResolver主题FixedThemeResolverHandlerMapping请求→方法映射RequestMappingHandlerMappingHandlerAdapter方法执行适配RequestMappingHandlerAdapterHandlerExceptionResolver异常处理ExceptionHandlerExceptionResolverRequestToViewNameTranslator视图名推断DefaultRequestToViewNameTranslatorViewResolver视图解析ContentNegotiatingViewResolverFlashMapManager重定向传参SessionFlashMapManagerprotected void initStrategies(ApplicationContext context) { initMultipartResolver(context); // 1. 文件上传 initLocaleResolver(context); // 2. 国际化 initThemeResolver(context); // 3. 主题 initHandlerMappings(context); // 4. 映射器 initHandlerAdapters(context); // 5. 适配器 initHandlerExceptionResolvers(context); // 6. 异常 initRequestToViewNameTranslator(context); // 7. 视图名 initViewResolvers(context); // 8. 视图 initFlashMapManager(context); // 9. Flash }启动时找不到自定义实现就用默认的找到了就替换成你的。三、请求处理主链路doDispatch()所有请求都汇聚到DispatcherServlet.doDispatch()。这是整个 Spring MVC 的心脏protectedvoiddoDispatch(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{HttpServletRequestprocessedRequestrequest;HandlerExecutionChainmappedHandlernull;booleanmultipartRequestParsedfalse;try{// 1. 文件上传请求先转成 MultipartHttpServletRequestprocessedRequestcheckMultipart(request);mappedHandlergetHandler(processedRequest);// 2. 找 Handler含拦截器链if(mappedHandlernull){noHandlerFound(processedRequest,response);// 404return;}// 3. 拿到适配器HandlerAdapterhagetHandlerAdapter(mappedHandler.getHandler());// 4. 执行拦截器 preHandleif(!mappedHandler.applyPreHandle(processedRequest,response)){return;}// 5. 真正执行 Controller 方法mvha.handle(processedRequest,response,mappedHandler.getHandler());// 6. 执行拦截器 postHandlemappedHandler.applyPostHandle(processedRequest,response,mv);}catch(Exceptionex){dispatchExceptionex;// 统一异常出口}// 7. 渲染视图 / 写响应体 拦截器 afterCompletionprocessDispatchResult(processedRequest,response,mappedHandler,mv,dispatchException);}核心就一句话找 Handler映射→ 适配执行 → 处理结果。下面拆每一步。四、HandlerMapping怎么找到那个方法RequestMappingHandlerMapping在启动时扫描所有Controller把每个RequestMapping方法解析成一个映射RequestMappingInfo存进注册表RequestMapping(/order/{id}) GET │ ▼ RequestMappingInfo{ patterns {/order/{id}}, methods {GET}, params {}, headers {}, consumes {}, produces {} } │ ▼ 映射注册表: MapRequestMappingInfo, HandlerMethod请求进来时通过getHandlerInternal()匹配按 URL 模板匹配支持{id}路径变量再按请求方法GET/POST过滤支持正则、通配符、最佳匹配排序匹配到以后返回一个HandlerExecutionChain——包含HandlerMethod 拦截器链。// 路径变量如何提取HandlerMethodhandlerMethod...;UrlPathHelperhelpernewUrlPathHelper();MapString,StringuriVariableshelper.decodeMatrixVariables(...);// 最后通过 MethodArgumentResolver 注入到 PathVariable 参数五、HandlerAdapter参数是怎么变成方法入参的RequestMappingHandlerAdapter拿着 34 种HandlerMethodArgumentResolver挨个判断这个参数该由谁来解析参数类型解析器解析逻辑PathVariablePathVariableMethodArgumentResolver从 URI 模板变量取RequestParamRequestParamMethodArgumentResolver从 queryString / form 取RequestBodyRequestResponseBodyMethodProcessor读 body → 消息转换器反序列化RequestHeaderRequestHeaderMethodArgumentResolver从请求头取ModelAttributeModelAttributeMethodProcessor绑定表单到对象CookieValueServletCookieValueMethodArgumentResolver从 Cookie 取HttpServletRequestServletRequestMethodArgumentResolver直接注入原始对象RequestPartRequestPartMethodArgumentResolver文件/多部分参数RequestBody的完整流程// 1. 从 body 读出字节流byte[]bodyreadBody(request);// 2. 根据 Content-Type 找消息转换器HttpMessageConverter?convertergetMessageConverter(request);// 3. 从字节流反序列化为对象Objectargconverter.read(type,request);Spring Boot 默认注册了MappingJackson2HttpMessageConverterJSON所以RequestBody OrderDTO能直接把 JSON 变成对象。这就是你啥都没配置就能用的原因。六、返回值处理器返回值怎么变成响应执行完 Controller 方法后HandlerMethodReturnValueHandler决定怎么处理返回值返回值处理器行为ResponseBody对象RequestResponseBodyMethodProcessor消息转换器序列化为 JSONString视图名ViewNameMethodReturnValueHandler找视图渲染ModelAndViewModelAndViewMethodReturnValueHandler直接渲染ResponseEntityHttpEntityMethodProcessor带状态码输出void无响应由 response 对象直接写// ResponseBody 的核心逻辑publicvoidhandleReturnValue(ObjectreturnValue,...){// 1. 找到能写这个类型的消息转换器HttpMessageConverter?converterselectConverter(returnValue);// 2. 把对象序列化后写进 response bodyconverter.write(returnValue,contentType,response);}因为RestControllerController ResponseBody所以每个方法返回值都会走这条 JSON 输出通道。七、消息转换器序列化的最后一公里HttpMessageConverter接口的两个核心方法publicinterfaceHttpMessageConverterT{// 能不能读这个类型booleancanRead(Class?clazz,MediaTypemediaType);// 能不能写这个类型booleancanWrite(Class?clazz,MediaTypemediaType);// 反序列化字节流 → 对象Tread(Class?extendsTclazz,HttpInputMessageinputMessage);// 序列化对象 → 字节流voidwrite(Tt,MediaTypecontentType,HttpOutputMessageoutputMessage);}默认注册的一批转换器按顺序匹配转换器处理内容ByteArrayHttpMessageConverter字节数组StringHttpMessageConverter文本FormHttpMessageConverter表单MappingJackson2HttpMessageConverterJSONMappingJackson2XmlHttpMessageConverterXMLResourceHttpMessageConverter资源下载顺序很重要——Spring 会按getSupportedMediaTypes()匹配Accept头是 JSON 时选 Jackson是 XML 时选 XML 转换器。八、异常处理ExceptionHandlerExceptionResolverController 抛异常后DispatcherServlet兜底调用HandlerExceptionResolverController 抛异常 │ ▼ HandlerExceptionResolver 链 ├─ ExceptionHandlerExceptionResolverExceptionHandler/RestControllerAdvice ├─ ResponseStatusExceptionResolverResponseStatus └─ DefaultHandlerExceptionResolver内置状态码RestControllerAdvice的全局异常处理器就是第一个 resolver 在工作RestControllerAdvicepublicclassGlobalExceptionHandler{ExceptionHandler(BusinessException.class)publicResponseEntityErrorResulthandleBiz(BusinessExceptione){returnResponseEntity.badRequest().body(newErrorResult(e.getCode(),e.getMessage()));}}匹配逻辑ExceptionHandlerExceptionResolver收集所有ExceptionHandler方法按异常类型最接近优先子类 父类。九、一次完整的请求从进入到响应把上面的环节拼起来一个POST /order的完整旅程① 请求进入容器Tomcat ② 路由到 DispatcherServlet.doDispatch() ③ checkMultipart不是文件上传跳过 ④ getHandler匹配 RequestMapping(/order) POST └─ HandlerExecutionChain HandlerMethod 拦截器链 ⑤ 拦截器 preHandle → 认证/日志/限流 ⑥ RequestMappingHandlerAdapter 解析参数 └─ RequestBody OrderDTO └─ MappingJackson2HttpMessageConverter.read() ⑦ 执行 OrderController.create() └─ 业务逻辑返回 OrderVO ⑧ ResponseBody 处理器 └─ MappingJackson2HttpMessageConverter.write(JSON) ⑨ 拦截器 postHandle → afterCompletion ⑩ 响应返回浏览器看到没你在 Controller 里写的那几行业务代码只是整个链路里的一小段。剩下的全部由框架代劳。十、几个高频面试追问10.1 为什么 Transactional 和 Async 经常失效在 MVC 里为什么会调用自身方法失效同一个类内部方法调用不走代理Transactional/Async 都是 AOP 代理拦截的。所以// 错误内部调用不走代理publicvoidouter(){this.inner();// Transactional 失效}// 正确注入自身代理publicvoidouter(){self.inner();// 通过代理调用事务生效}10.2 为什么 URL 匹配/order/{id}能同时匹配/order/1和/order/abc路径变量是字符串匹配{id}不限制类型。1和abc都是合法字符串都匹配。类型转换发生在参数解析器PathVariable里转换失败会抛MethodArgumentTypeMismatchException400。10.3 拦截器Interceptor和过滤器Filter有什么区别FilterInterceptor规范Servlet 规范Spring 框架时机进入 DispatcherServlet 之前DispatcherServlet 之后能拿到request/responseHandlerMethod能改目标方法调用链多个 Filter 链式preHandle → postHandle → afterCompletion10.4 为什么返回对象是 JSON 而不是视图RestController的ResponseBody让返回值处理器选择了 JSON 输出通道。而Controller返回 String 会被当成视图名走 ViewResolver 渲染。十一、总结Spring MVC 的请求处理本质是一个流水线DispatcherServlet 做调度HandlerMapping 做路由HandlerAdapter 做参数与执行消息转换器做序列化异常解析器兜底。你只需要关注我的业务方法怎么写剩下的框架全帮你编排好了。理解了这条链路遇到 404、400、JSON 乱码、参数解析失败这类问题你就能一眼定位到是哪个环节出了问题。
返回列表