[ SpringMVC ] SpringMVC如何通过是否有@RestController注解来判断返回ModelAndView还是Json

引言

之前在面试的遇到面试官问我SpringMVC的执行流程,我那时候回答的是SpringMVC的DispatcherServlet的dodispatch方法找到ControllerMethod之后将返回值通过convert成Json返回响应体,事后想了一下回答的其实并不正确,因为SpringMVC之前学习的时候有使用ModelAndView返回视图,我回答的只是前后端分离的情况, 现在回头学习的时候就在想,DispatchServlet是如何通过一个@RestController(@ResponseBody和@Controller)就能知道返回的是响应体而不是ModelAndView的,打开源码并找到了思路之后写下了这篇博客以记录一下(平时学习的时候看那些网上的资料或者书籍,感觉学习完再写博客就像抄书一样,不如想起来的时候再去网站看,自己琢磨了一会琢磨出来的东西还是比较适合记录一下)

调试过程

因为最近在实习,所以就直接拿实习时做的一个练习小模块来实验,这里用的时SpringBoot3和JDK21,来看一下Controller和Method
img

这里可以看见是个@RestController注解的Controller (这里一些太简单的东西就不讲这么细了,因为大部分是写出来给自己看的,如果你看到了这篇博客并且能理解那就是我的荣幸XD)

这时候我们找到DispatchServlet的doDispatch方法并打上断点
img

这里顺带可以复习一下SpringMVC前面的执行流程,一个Request到doDispatch方法的时候是通过mappedHandler = getHandler(processedRequest);来获取HandlerExecutionChain执行链
img

这里返回HandlerExecutionChain之后再根据当前的Handler确定HandlerAdapter
img
img

这里的supports(handler)方法就是判断当前的adapter是否适配当前的handler
img

这里的上面一个方法是是执行拦截器interceptor,就是应用已注册拦截器的 preHandle 方法。
img

这里不是重点看一下就好了,重点在mv = ha.handle(processedRequest, response, mappedHandler.getHandler());这个方法上,这里我们也可以看见注释Actually invoke the handler,也就是真正执行handler的地方,让我们进去细看
img
img

我们来到了方法handleInternal,这里this.synchronizeOnSession为false直接来到注释// No synchronization on session demanded at all...的方法 mav = invokeHandlerMethod(request, response, handlerMethod);当中
img
img

这里关键的方法是invocableMethod.invokeAndHandle(webRequest, mavContainer);,让我们进入到方法内部去看看
img

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);是获取执行后的返回值
img

这里是我们自定义的一个ResponseVO,在获取这个resultValue之后,会对ResponseStatus和ResponseStatusReason进行几次判断然后设置RequestHandled的boolean值进行设置,那么这个RequestHandled是什么呢?让我们点进去看一下源码的注解解释
img

接下来我们的代码走到了这里
img

也就是说这里处理器并没有完全处理请求,这时候有个断言来检查当前是否有handlers,这里通过之后进入try语句块,让我们进入这个方法

this.returnValueHandlers.handleReturnValue(
       returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

img

这里我们能能看到这里在选择HandlerMethodReturnValueHandler
img

当遍历到RequestResponseBodyMethodProcessor 这个MethodProcessor的时候可以看到这个类是支持这个返回ReturnType的,为什么呢?让我们来进入第二个if判断条件的handler.supportsReturnType(returnType) 当中
img

这里的判断条件就是你的类或方法是否有@ResponseBody注解,如果有的话就返回true,所以这里if判断通过返回当前HandlerMethodReturnValueHandler,所以返回的就是RequestResponseBodyMethodProcessor

让我们返回到handleReturnValue方法上,我们可以看到当selectHandler了之后如果当前的handler还是null的话就会直接抛出IllegalArgumentException

这里我们的handler不为null则直接调用handler的方法handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
img

这里就将RequestHandled给设置为true也就是说处理完毕了,接下来执行到writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);

writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage) 方法的作用是通过已注册的 HttpMessageConverter 将返回值转换为指定的数据格式,并直接写入 HTTP 响应体

这里我们直接定位到这个方法里的
img

这里通过for遍历已经注册的HttpMessageConverter ,这里满足条件的是MappingJackson2HttpMessageConverter 之后将converterTypeToUse设置为

ConverterType.GENERIC ,继续往下走,判断converterTypeToUse不为null之后将body处理完之后进入switch语句,然后将消息write进body之后返回.

到这里我们的doDispatch就算执行完毕了,调用return getModelAndView(mavContainer, modelFactory, webRequest);这时候我们会发现,前后端分离使用响应体的话就直接给doDispatch里的mv返回为null了,这里不用担心,因为消息已经写入了ResponseBody,接下来将正常的流程走完就没事了.

总结梳理

  1. 请求入口
    请求到达 DispatcherServletdoDispatch 方法,开始处理流程。
  2. 获取处理器链
    通过 getHandler() 获取 HandlerExecutionChain,确定具体的 Controller 方法。
  3. 选择适配器
    使用 HandlerAdapter(如 RequestMappingHandlerAdapter)执行目标方法。适配器通过 supports(handler) 判断是否支持当前处理器。
  4. 执行 Controller 方法
    调用 invokeHandlerMethod 执行 Controller 方法,获取返回值。
    关键判断点:若方法或类标注了 @ResponseBody(或组合注解 @RestController),会触发以下流程:
    • 返回值由 RequestResponseBodyMethodProcessor 处理。
    • 通过 HttpMessageConverter(如 MappingJackson2HttpMessageConverter)将返回值序列化为 JSON,直接写入响应体。
    • 跳过视图解析ModelAndView 返回 null
  5. 返回值处理逻辑
    • 存在 @ResponseBody/@RestController
      • 遍历 HandlerMethodReturnValueHandler,匹配到 RequestResponseBodyMethodProcessor
      • 调用 writeWithMessageConverters 将返回值写入响应体,响应流程结束。
    • @ResponseBody
      • 返回 ModelAndView,后续由视图解析器处理视图渲染。
  6. 结果返回
    • 若响应体已写入(JSON 场景),doDispatch 返回 null,无需视图解析。
    • 若返回 ModelAndView,继续执行视图渲染流程。

这次研究过后感觉自己的Debug调试源码能力得到了锻炼,还有就是加深了对SpringMVC的理解,因为平时基本都是前后端分离的项目所以问到这个执行流程的时候就直接把ModelAndView视图解析阶段给跳过了,继续加油学习! 看Spring源码还是挺复杂的对我目前的阶段来说,因为封装太多东西了,慢慢学习不浮躁!

而且在看到遍历寻找adapter的时候认识到了接口的重要性和Java多态的特性

posted @ 2025-03-17 16:19  MingHaiZ  阅读(93)  评论(0)    收藏  举报