📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍
文章目录
写在前面的话
通过上一篇博文《学会 SpringMVC 系列 · 基础篇》的学习,可以掌握 SpringMVC 的项目搭建和部分用法,从搭建过程中我们看到,SpringMVC 的入口是在 web.xml 中添加 DispatcherServlet,它是一个Servlet,那请求流程也遵循 Servlet 相关规范展开。
接下来,让我们进一步分析相关源码,顺带引出相关扩展点和实战运用。
学前准备
1、SpringMVC 源码分析分为初始化流程和请求流程两部分,本篇先重点介绍后者。可以把 SpringMVC的请求流程,比作一个请求(Servlet)的完整生命周期,那就是包括“请求前 - 实际请求 - 请求后”,以此思路展开;
2、本篇 SpringMVC 源码分析系列文章,将以 《搭建拥有数据交互的 SpringBoot 》的 SpringBoot3.x 为基础,学习相关源码,对应 SpringMVC 版本为 6.1.11,不过核心流程上,基本大同小异;
3、先添加一个入参和返回值都是实体对象的接口,如下所示,正所谓麻雀虽小,但也覆盖到入参解析逻辑了,启动Boot项目,Postman测试一下,恩,功能都正常,可以开始源码学习了!
@ResponseBody@RequestMapping("/studyJson")publicZyTeacherInfostudyJson(@RequestBodyZyTeacherInfo teacherInfo){
teacherInfo.setTeaName("战神");return teacherInfo;}
相关文章
《学会 SpringMVC 系列 · 剖析篇(上)》
请求流程分析
Tips:先通过示例方法的执行,梳理一下相对完整的链路,后续在此基础上扩展自定义逻辑。
Step1、DispatcherServlet#doDispatch
前面提到 DispatcherServlet 是 SpringMVC 的入口,不管是否为 SpringBoot 项目,都是如此。
从下面这张图很明显可以看出 DispatcherServlet 和 Servlet 的父子关系。
有过 JavaWeb 开发经验的人应该了解,Servlet 的请求入口方法是 service 方法。在访问接口后,可以把断点停留在 DispatcherServlet#doDispatch 方法,可以从IDEA的调试器,观察到请求的调用栈信息。
这边先不展开细节,后续再按专栏展开,总之是在 DispatcherServlet、FrameworkServlet、HttpServlet 等类之间反复横跳,最后到了 doDispatch,当然,这里面才是请求的核心逻辑。
Step2、DispatcherServlet#getHandler
关键代码:
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
逻辑说明:
- 目的是获取 HandlerExecutionChain 执行链对象,除了包含 HandlerMethod 方法(具体 Controller 的方法),还包含拦截器信息;
- 执行过程,如下图所示,遍历 HandlerMapping 列表(在初始化时加载列表),依次执行 HandlerMapping 的 getHandler 方法,仅有 RequestMappingHandlerMapping 满足,同时在其父类 AbstractHandlerMapping 的方法 getHandlerExecutionChain 中,会加载拦截器链,如图二所示;
如上图所示,获取到的 HandlerExecutionChain 对象,指向具体业务接口,并且包含三个内置拦截器。
到此,DispatcherServlet#getHandler 流程基本结束。
题外话 - 如何判定 RequestMappingHandlerMapping:
在 SpringBoot 开发中,大部分接口添加了 @Controller 注解,示例方法也是如此,从
RequestMappingHandlerMapping#isHandler
的代码也可以看出来,只要类包含 @Controller 注解,就可以满足加载要求。
Tips:SpringMVC的5.x,也允许只存在 RequestMapping 注解的情况,6.x 只剩下Controller了。
Tips:该方法是服务启动的时候,逐个Bean去调用该方法,判定归属,后续再展开介绍。
protectedbooleanisHandler(Class<?> beanType){returnAnnotatedElementUtils.hasAnnotation(beanType,Controller.class);}
Tips:另外,RequestMappingHandlerMapping 会在初始化的时候会将
url/controller的映射
存到handlerMethods 变量中,
url/mapping的映射
存在urlMap变量中,如下所示,目的是方便快速查找。
privatefinalMap<T,HandlerMethod> handlerMethods =newLinkedHashMap<T,HandlerMethod>();privatefinalMultiValueMap<String,T> urlMap =newLinkedMultiValueMap<String,T>();
温馨提示:很多代码还可继续深挖,但建议分析源码还是要把握主线、浅尝辄止,不然深陷其中,无法自拔,当然,后续会针对每个重要板块单独展开分析,本篇只是展示一个全貌。
Step3、DispatcherServlet#getHandlerAdapter
关键代码:
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
逻辑说明:
- 目的是获取 HandlerAdapter 处理器对象(虽然单词是适配器,实际做的是处理器的事情),逻辑有点类似前面找Mapping 的方式;
- 如下图所示,从 HandlerAdapter 列表遍历,依次寻找符合要求的,匹配到 RequestMappingHandlerAdapter;
- 如下方代码也可以看出,是根据 supports 方法判断 adapter 是否支持这个 handler,很明显这个要求很简单,RequestMappingHandlerAdapter 是肯定满足的;
// 存在父类 AbstractHandlerMethodAdapter#supportspublicfinalbooleansupports(Object handler){// 判断handler是否属于HandlerMethod 并且 supportsInternal 为truereturn(handler instanceofHandlerMethod&&supportsInternal((HandlerMethod) handler));}
题外话:getHandlerAdapter 添加注释信息
如下所示,看起来和前面获取 HandlerExecutionChain 的获取逻辑差不多。
protectedHandlerAdaptergetHandlerAdapter(Object handler)throwsServletException{//遍历所有handlerAdapters 选择合适的handlerAdaptersfor(HandlerAdapter ha :this.handlerAdapters){if(logger.isTraceEnabled()){
logger.trace("Testing handler adapter ["+ ha +"]");}//判断这个adapter是否支持这个handlerif(ha.supports(handler)){return ha;}}thrownewServletException("No adapter for handler ["+ handler +"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}
Step4、DispatcherServlet#handle
接下来到了核心代码片段,直接看如下代码:可以看到 handle 才是绝对的核心,前后包裹了前置和后置拦截器。
if(!mappedHandler.applyPreHandle(processedRequest, response)){return;}// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mappedHandler.applyPostHandle(processedRequest, response, mv);
逻辑说明:
1、此时已经得到 HandlerAdapter,先执行所有拦截器的 applyPreHandle 方法;
2、接着调用其 handle 方法,再执行拦截器的 applyPostHandle 方法;
3、这里的 handle 是主要逻辑,由于 RequestMappingHandlerAdapter 没有 handle 方法,所以进入父类的 handle,再经过一系列方法,最终进入InvocableHandlerMethod#invokeForRequest;
//请求链路:// AbstractHandlerMethodAdapter#handle// RequestMappingHandlerAdapter#invokeHandleMethod// ServletInvocableHandlerMethod#invokeAndHandle// InvocableHandlerMethod#invokeForRequest,下方代码(参数解析和实际请求)// HandlerMethodReturnValueHandlerComposite#returnValueHandlers(返回值处理)publicfinalObjectinvokeForRequest(NativeWebRequest request,ModelAndViewContainer mavContainer,Object... providedArgs)throwsException{// 利用参数解析器解析参数,最终得到参数列表Object[] args =getMethodArgumentValues(request, mavContainer, providedArgs);if(logger.isTraceEnabled()){StringBuilder sb =newStringBuilder("Invoking [");
sb.append(getBeanType().getSimpleName()).append(".");
sb.append(getMethod().getName()).append("] method with arguments ");
sb.append(Arrays.asList(args));
logger.trace(sb.toString());}//此处执行反射调用controller的方法Object returnValue =doInvoke(args);if(logger.isTraceEnabled()){
logger.trace("Method ["+getMethod().getName()+"] returned ["+ returnValue +"]");}return returnValue;}
Step5、InvocableHandlerMethod#getMethodArgumentValues
接上回,getMethodArgumentValues 这步通俗一点来说,就是解析参数,得到最后的参数列表。
部分代码如下所示:
protectedObject[]getMethodArgumentValues(NativeWebRequest request,@NullableModelAndViewContainer mavContainer,Object... providedArgs)throwsException{Object[] args =newObject[parameters.length];for(int i =0; i < parameters.length; i++){// 判断是否支持解析这个参数,如果支持会把参数解析器加入到缓存中if(!this.resolvers.supportsParameter(parameter)){thrownewIllegalStateException(formatArgumentError(parameter,"No suitable resolver"));}try{解析请求参数
args[i]=this.resolvers.resolveArgument(parameter, mavContainer, request,this.dataBinderFactory);}}return args;}
其中,最重要的方法应该是 resolveArgument,其部分代码如下,逻辑也是把所有参数解析器拿出来溜溜,看看哪个解析器的 supportsParameter 方法满足。
publicObjectresolveArgument(MethodParameter parameter,@NullableModelAndViewContainer mavContainer,NativeWebRequest webRequest,@NullableWebDataBinderFactory binderFactory)throwsException{HandlerMethodArgumentResolver resolver =getArgumentResolver(parameter);return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);}publicHandlerMethodArgumentResolvergetArgumentResolver(MethodParameter parameter){HandlerMethodArgumentResolver result =this.argumentResolverCache.get(parameter);if(result ==null){for(HandlerMethodArgumentResolver resolver :this.argumentResolvers){if(resolver.supportsParameter(parameter)){
result = resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;}
不出意外,满足的是 RequestResponseBodyMethodProcessor,运行截图如下,从27个参数解析器中选中了这个。
流程重新梳理一下:
- 遍历所有参数列表,依次利用 supportsParameter 方法,判断是否有解析器是否满足;
- 再进行下面的一系列判定逻辑,最终可以找到 RequestResponseBodyMethodProcessor 这一解析器是符合的,紧接着把参数解析器放到 argumentResolverCache 缓存中;
@OverridepublicbooleansupportsParameter(MethodParameter parameter){return parameter.hasParameterAnnotation(RequestBody.class);}@OverridepublicbooleansupportsReturnType(MethodParameter returnType){return(AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(),ResponseBody.class)||
returnType.hasMethodAnnotation(ResponseBody.class));}
- 最终调用RequestResponseBodyMethodProcessor 的 resolveArgument 进行参数解析;
Tips:这里其实又是一个适配器的套路(适配器模式),Spring为我们提供了多种场景的支持。
题外话:如何是 @RequestParam 注解呢?
如果接口的参数使用 @RequestParam 注解,那么这里满足的是 RequestParamMethodArgumentResolver ,运行截图如下,当然它也是第一顺位。
值得一提,如果没找到合适的参数处理器,那么最后还是会用 RequestParamMethodArgumentResolver 兜底,此时后话,暂且不提。
Step6、InvocableHandlerMethod#doInvoke
接前面的
InvocableHandlerMethod#invokeForRequest
,在执行完
getMethodArgumentValues
获得最终参数列表后,就直接调用
InvocableHandlerMethod#doInvoke
进行真实逻辑调用,得到返回值。
利用的是反射机制,细节不展开,后续专栏再补充说明。
Step7、HandlerMethodReturnValueHandlerComposite#handleReturnValue
实际业务接口执行完,也拿到了返回值,接下来就该针对返回值进行加工处理。
部分代码如下,又是一样的讨论,先从返回值处理器的列表里面,找到匹配的,然后执行处理逻辑。
publicvoidhandleReturnValue(@NullableObject returnValue,MethodParameter returnType,ModelAndViewContainer mavContainer,NativeWebRequest webRequest)throwsException{//找到合适的返回值处理器HandlerMethodReturnValueHandler handler =selectHandler(returnValue, returnType);if(handler ==null){thrownewIllegalArgumentException("Unknown return value type: "+ returnType.getParameterType().getName());}//执行处理逻辑
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}privateHandlerMethodReturnValueHandlerselectHandler(@NullableObject value,MethodParameter returnType){boolean isAsyncValue =isAsyncReturnValue(value, returnType);for(HandlerMethodReturnValueHandler handler :this.returnValueHandlers){if(isAsyncValue &&!(handler instanceofAsyncHandlerMethodReturnValueHandler)){continue;}if(handler.supportsReturnType(returnType)){return handler;}}returnnull;}
Step8、RequestResponseBodyMethodProcessor#writeWithMessageConverters
接上回,进去 RequestResponseBodyMethodProcessor 返回值处理器。
这里还要再经历一次消息转换器遍历,找到合适的,进行逻辑处理。
接着进入write逻辑,如下所示,经过
outputMessage.getBody().flush()
,数据已经输出给调用方了。
publicfinalvoidwrite(finalT t,@NullableMediaType contentType,HttpOutputMessage outputMessage)throwsIOException,HttpMessageNotWritableException{finalHttpHeaders headers = outputMessage.getHeaders();addDefaultHeaders(headers, t, contentType);if(outputMessage instanceofStreamingHttpOutputMessage){StreamingHttpOutputMessage streamingOutputMessage =(StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream ->writeInternal(t,newHttpOutputMessage(){@OverridepublicOutputStreamgetBody(){return outputStream;}@OverridepublicHttpHeadersgetHeaders(){return headers;}}));}else{writeInternal(t, outputMessage);
outputMessage.getBody().flush();}}
write 完毕之后,请求链路又回到DispatcherServlet#doDispatch,再来后置拦截器等处理,这边暂且不继续展开。
请求分析复盘
整个流程梳理(精简版)
Tips:上述分析流程精简一下,可以得出如下结论。
1、总入口 DispatcherServlet,最底层其实是 HttpServlet#service
2、根据请求URL,找到处理方法Method,DispatcherServlet#getHandler
3、参数处理,HandlerMethodArgumentResolver,RequestResponseBodyMethodProcessor
4、执行原方法逻辑,invoke
5、返回值处理,HandlerMethodReturnValueHandler,RequestResponseBodyMethodProcessor
【补充:上述流程分析】
1、HandlerMapping 阶段,匹配到一个HandlerMapping,通过Url找到某个controller的某个方法。返回HandlerExecutionChain 对象;
2、根据HandlerMethod匹配到某个HandlerAdapter,也就是我们的RequestMappingHandlerAdapter;
3、this.argumentResolvers.supportsParameter(parameter)匹配参数处理器,这里会匹配到RequestResponseBodyMethodProcessor;
4、从messageConverters集合中匹配出参数转换器,这里是MappingJackson2HttpMessageConverter。调用Jackson API转换成对象;
5、得到参数后,利用反射执行某个controller中某个方法;
从这里我们可以看出,Spring框架是相当灵活的,适配器模式是被其发挥得淋漓尽致。支持我们自定义HandlerMapping、HandlerAdapter、messageConverters等等,以适应更多的应用场景。
Tips:虽然可以注册多个 argumentResolver 和 messageConverters,但最终只会选择一个合适的执行。
【补充:@RequestBody 介绍】
@RequestBody 注解是 Spring MVC 中的一个注解,用于指示控制器方法参数应该绑定到 HTTP 请求的主体(body)部分。当客户端向服务器发送 POST 或 PUT 请求时,请求的数据通常包含在请求主体中,而不是在 URL 参数中。@RequestBody 注解告诉 Spring MVC 框架将请求主体中的数据反序列化为指定的 Java 对象,并将其作为方法的参数传递给控制器方法。
具体来说,@RequestBody 注解的作用包括以下几个方面:
反序列化请求主体: 当客户端发送一个包含 JSON、XML 等格式的数据主体的 POST 或 PUT 请求时,Spring MVC 框架将请求主体中的数据反序列化为指定的 Java 对象。这个过程通常使用 Jackson、JAXB 等库来完成,将请求主体中的数据转换为相应的 Java 对象。
绑定到方法参数: 反序列化后的 Java 对象将作为 @RequestBody 注解标注的方法参数的值传递给控制器方法。通过使用 @RequestBody 注解,你可以直接将请求主体中的数据映射到方法参数,而不必手动解析请求主体或处理输入流。
处理多种数据格式: @RequestBody 注解不仅可以处理 JSON 格式的数据,还可以处理 XML、表单数据等多种格式的数据。Spring MVC 框架会根据请求的 Content-Type 头部来确定请求主体的数据格式,并使用相应的消息转换器(MessageConverter)来进行反序列化。
综上所述,@RequestBody 注解在 Spring MVC 中的作用是将请求主体中的数据反序列化为指定的 Java 对象,并绑定到控制器方法的参数上,使得控制器方法能够直接处理请求主体中的数据。
【补充:关于 HandlerMapping 和 HandlerAdapter 的初始化】
protectedvoidinitStrategies(ApplicationContext context){...省略...//初始化HandlerMappingsinitHandlerMappings(context);//初始化HandlerAdaptersinitHandlerAdapters(context);...省略...}
HandlerMapping和HandlerAdapter是可以多个的,Spring默认会注册几个HandlerMapping(如BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping),有请求来的时候,去匹配到合适的那个。
在Spring MVC时代,我们通常会显式去注册一个HandlerMapping和HandlerAdapter,分别是RequestMappingHandlerMapping和RequestMappingHandlerAdapter。
当然要使用MappingJackson2HttpMessageConverter转换器还需要jackson-databind、jackson-core、jackson-mapper-lgpl、jackson-mapper-asl、jackson-core-lgpl、jackson-core-asl这些Jar包。
HandlerAdapter 和 HandlerMapping 也是类似的逻辑,一样是支持多个,默认注册HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter。
doDispatch(部分代码)
protectedvoiddoDispatch(HttpServletRequest request,HttpServletResponse response)throwsException{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);// 找出处理这个请求的执行链对象 HandlerExecutionChain,包含方法和拦截器
mappedHandler =getHandler(processedRequest,false);if(mappedHandler ==null|| mappedHandler.getHandler()==null){noHandlerFound(processedRequest, response);return;}// 根据 HandlerMethod 获取处理器 HandlerAdapterHandlerAdapter ha =getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet ="GET".equals(method);if(isGet ||"HEAD".equals(method)){//RequestMappingHandlerAdapter#getLastModified返回-1long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if(logger.isDebugEnabled()){
logger.debug("Last-Modified value for ["+getRequestUri(request)+"] is: "+ lastModified);}// 检测是否未改变 并且 是get请求if(newServletWebRequest(request, response).checkNotModified(lastModified)&& isGet){return;}}// 执行拦截器的前置处理方法 applyPreHandleif(!mappedHandler.applyPreHandle(processedRequest, response)){//不为true则returnreturn;}// 执行实际的处理器处理请求,这步骤是最核心的
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if(asyncManager.isConcurrentHandlingStarted()){return;}// 视图对象的处理,mv不为null并且view不存在则应用默认的viewNameapplyDefaultViewName(request, mv);// 执行拦截器的后置处理方法 postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);}catch(Exception ex){
dispatchException = ex;}//请求成功响应之后的方法,即要解析为ModelAndView或异常。processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch(Exception ex){// 主要是发生异常时执行拦截器afterCompletion方法triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch(Error err){triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);}finally{if(asyncManager.isConcurrentHandlingStarted()){// 执行拦截器 afterCompletionif(mappedHandler !=null){
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else{// Clean up any resources used by a multipart request.if(multipartRequestParsed){cleanupMultipart(processedRequest);}}}}
总结陈词
此篇文章介绍了
SpringMVC
请求流程的源码分析,仅供参考。
无论是 handlerAdapters 还是 argumentResolvers,或者其他,基本几个用法都类似。
- 预先加载List
- 遍历List判断是否满足
- 适配器思想
- 缓存复用思想
- 。。。
篇幅所限,主要介绍了一个具体请求的流程,后续系列文章会针对其中重要步骤继续展开介绍,同时添加实战说明。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。
版权归原作者 战神刘玉栋 所有, 如有侵权,请联系我们删除。