万字长文,深度解析SpringMVC 源码,让你醍醐灌顶!!-程序员宅基地

技术标签: spring  java  web  http  html  

文末可以领取所有系列高清 pdf。

大家好,我是路人,这是 SpringMVC 系列第 16 篇。

本文将通过阅读源码的方式带大家了解 springmvc 处理请求的完整流程,干货满满。

目录

  • 1、先了解下 SpringMVC 常用的 10 组件

    • 1.1、DispatcherServlet:前端控制器

    • 1.2、HandlerMapping:处理器映射器

    • 1.3、HandlerExecutionChain:处理器执行链

    • 1.4、handler:处理器

    • 1.5、HandlerAdapter:处理器适配器

    • 1.6、ModelAndView:模型和视图

    • 1.7、ViewResolver:视图解析器

    • 1.8、View:视图

    • 1.9、HandlerExceptionResolver:处理器异常解析器

    • 1.10、HttpMessageConverter:http 报文转换器

  • 2、处理流程:源码解析

    • 2.1、请求到达入口:doDispatch

    • 2.2、①:解析 multipart 类型的请求

    • 2.3、②:根据请求获取 HandlerExecutionChain 对象

    • 2.4、③:根据处理器获取 HandlerAdapter

    • 2.5、④:调用拦截器的 preHandle 方法

    • 2.6、⑤:调用 handler 实际处理请求,获取 ModelAndView 对象

    • 2.7、⑥:调用拦截器的 postHandle 方法

    • 2.8、⑦:渲染视图

  • 3、处理流程:纯文字描述

  • 4、小结

  • 5、案例代码

  • 6、SpringMVC 系列

  • 7、更多好文章

  • 8、【路人甲 Java】所有系列高清 PDF

1、先了解下 SpringMVC 常用的 10 组件

1.1、DispatcherServlet:前端控制器

这个大家是最熟悉的,是一个 servlet,是 springmvc 处理请求的入口,不需要咱们开发,由框架提供。

作用:统一处理请求和响应,整个流程控制的中心,由它来调用其他组件处理用户的请求。

1.2、HandlerMapping:处理器映射器

作用:根据请求的信息(如 url、method、header 等)查找请求处理器,即找到自定义的 controller 中处理请求的方法。

HandlerMapping 接口源码如下,getHandler:根据请求查找请求处理器,会返回一个 HandlerExecutionChain 对象。

public interface HandlerMapping {
 HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

常见的实现类:

  • RequestMappingHandlerMapping:请求映射处理器映射,用来处理@RequestMapping 定义的处理器的

1.3、HandlerExecutionChain:处理器执行链

HandlerMapping#getHandler 方法会根据请求得到一个 HandlerExecutionChain 对象。

HandlerExecutionChain 源码如下,主要包含了 3 个信息

  • handler:请求处理器,通常就是我们自定义的 controller 对象及方法

  • interceptorList:拦截器,当前请求匹配到的拦截器列表

  • interceptorIndex:拦截器索引,用来记录执行到第几个拦截器了

public class HandlerExecutionChain {

 private final Object handler;

 private final List<HandlerInterceptor> interceptorList = new ArrayList<>();

 private int interceptorIndex = -1;

}

1.4、handler:处理器

通常需要我们自己开发,一般指我们自定义的 controller,在 DispatcherServlet 的控制下 handler 对具体的请求进行处理。

1.5、HandlerAdapter:处理器适配器

他负责对 handler 的方法进行调用,由于 handler 的类型可能有很多种,每种 handler 的调用过程可能不一样,此时就需要用到适配器 HandlerAdapte,适配器对外暴露了统一的调用方式(见其 handle 方法),内部将 handler 的调用过程屏蔽了,HandlerAdapter 接口源码如下,主要有 2 个方法需要注意:

  • supports:当前 HandlerAdapter 是否支持 handler,其内部主要就是判 HandlerAdapter 是否能够处理 handler 的调用

  • handle:其内部负责调用 handler 的来处理用户的请求,返回返回一个 ModelAndView 对象

public interface HandlerAdapter {

 boolean supports(Object handler);

 @Nullable
 ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

}

常见的实现类:

  • RequestMappingHandlerAdapter:其内部用来调用@RequestMapping 标注的方法

1.6、ModelAndView:模型和视图

这个对象中主要用来存放视图的名称和共享给客户端的数据。

public class ModelAndView {

 /*视图*/
 @Nullable
 private Object view;

 /*模型,用来存放共享给客户端的数据*/
 @Nullable
 private ModelMap model;

}

1.7、ViewResolver:视图解析器

这个是框架提供的,不需要咱们自己开发,它负责视图解析,根据视图的名称得到对应的视图对象(View)。

ViewResolver 接口源码

public interface ViewResolver {

 @Nullable
 View resolveViewName(String viewName, Locale locale) throws Exception;

}

这个接口有很多实现类,比如 jsp 的、freemarker、thymeleaf 的等,他们都有各自对应的 ViewResolver。

而比较常的实现类是InternalResourceViewResolver,这个大家应该比较熟悉吧,目前为止我们前面的文章用到的都是这个视图解析器,用来处理 jsp 格式的视图页面,带大家再回顾一下这个类的配置,如下

<!-- 添加视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"/>
    <property name="suffix" value=".jsp"/>
</bean>

InternalResourceViewResolver 比较重要,这里说下这个类的 resolveViewName 方法获取视图的过程,大家也可以去阅读InternalResourceViewResolver#resolveViewName方法获得,大致的过程如下:

step1:判断视图 viewName 是否以redirect:开头,如果是,则返回RedirectView类型的视图对象,RedirectView 是用来重定向的,RedirectView 内部用到的是response.sendRedirect(url)进行页面重定向;否则继续向下 step2

step2:判断 viewName 是否以forward:开头,如果是,则返回InternalResourceView类型的视图对象,InternalResourceView 是用来做跳转的,InternalResourceView 内部用到的是request.getRequestDispatcher(path).forward(request, response)进行页面跳转;否则继续向下 step3

step3:判断当前项目是否存在 jstl 所需的类,如果是,则返回 JstlView 类型的视图,否则返回 InternalResourceView 类型的视图,这两个视图的 render 方法最终会通过request.getRequestDispatcher(path).forward(request, response)进行页面的跳转,跳转的路径是:InternalResourceViewResolver 的前缀 prefix + viewName+InternalResourceViewResolver 的后缀 prefix

1.8、View:视图

负责将结果展示给用户,View 接口源码如下,render 方法根据指定的模型数据(model)渲染视图,即 render 方法负责将结果输出给客户端。

public interface View {
 void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
   throws Exception;
}

View 接口常见的 2 个实现类

  • RedirectView:负责重定向的,内部通过response.sendRedirect(url)进行页面重定向

  • InternalResourceViewResolver:负责页面跳转的,内部通过request.getRequestDispatcher(path).forward(request, response)进行页面的跳转

1.9、HandlerExceptionResolver:处理器异常解析器

负责处理异常的,HandlerExceptionResolver 接口有个resolveException方法,用来解析异常,返回异常情况下对应的 ModelAndView 对象

public interface HandlerExceptionResolver {

 @Nullable
 ModelAndView resolveException(
   HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}

1.10、HttpMessageConverter:http 报文转换器

将请求报文转换为 Java 对象,或将 Java 对象转换为响应报文,在处理@RequestBody、RequestEntity、@ResponseBody、ResponseEntity 的时候会用到

public interface HttpMessageConverter<T> {

 /**
  * 是否可以将请求保温读取给方法参数指定的类型
  */
 boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

 /**
  * 是否可以将响应的保温转换为方法参数指定的类型输出
  */
 boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

 /**
  * 当前转换器支持的类型
  */
 List<MediaType> getSupportedMediaTypes();

 /**
  * 当前转换器支持的类型
  */
 default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
  return (canRead(clazz, null) || canWrite(clazz, null) ?
    getSupportedMediaTypes() : Collections.emptyList());
 }

 /**
  * 将http保温转换为给定的类型,然后返回
  */
 T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
   throws IOException, HttpMessageNotReadableException;

 /**
  * 将给定的对象t,转换为http报文输出到客户端
  */
 void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
   throws IOException, HttpMessageNotWritableException;

}

2、处理流程:源码解析

2.1、请求到达入口:doDispatch

springmvc 的所有请求,最终都会到达org.springframework.web.servlet.DispatcherServlet#doDispatch这个方法,整个请求的大致处理过程都在这个方法中,咱们从这个方法开始分析,源码如下,大家注意代码中的注释,带有标号,比如 ①、②、③ 这样需要的注释,大家需要注意了,这些是关键的步骤,稍后会对这些步骤做详细的说明

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //请求对象
    HttpServletRequest processedRequest = request;
    //处理器执行链对象
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    //获取异步处理管理器,servlet3.0后支持异步处理,可以在子线程中响应用户请求
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        //模型和视图
        ModelAndView mv = null;
        //异常对象
        Exception dispatchException = null;

        try {
            //①:解析multipart类型的请求,上传文件用的就是multipart类型的请求方式
            processedRequest = checkMultipart(request);
            //用来标记是否是multipart类型的请求
            multipartRequestParsed = (processedRequest != request);

            //②:根据请求获取HandlerExecutionChain对象
            mappedHandler = getHandler(processedRequest);
            //如果没有找到处理器,就404了
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            //③:根据处理器获取HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            //④:调用拦截器的preHandle方法,若返回false,处理结束
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            //⑤:调用handler实际处理请求,获取ModelAndView对象,这里会调用HandlerAdapter#handle方法处理请求,其内部会调用handler来处理具体的请求
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            //判断异步请求不是已经开始了,开始了就返回了
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            //如果mv对象中没有视图 & DispatcherServlet配置了默认的视图,则给mv安排一个默认的视图
            applyDefaultViewName(processedRequest, mv);

            //⑥:调用拦截器的postHandle方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        //⑦:处理分发结果,渲染视图(包含了正常处理和异常情况的处理),将结果输出到客户端
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        //⑧:调用拦截器的afterCompletion方法
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        //⑧:调用拦截器的afterCompletion方法
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        //对于异步处理的情况,调用异步处理的拦截器AsyncHandlerInterceptor的afterConcurrentHandlingStarted方法
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            //对于multipart的请求,清理资源,比如文件上传的请求,在上传的过程中文件会被保存到临时文件中,这里就会对这些文件继续清理
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

下面我们来对上面带有编号的步骤进行分析。

2.2、①:解析 multipart 类型的请求

//①:解析multipart类型的请求,上传文件用的就是multipart类型的请求方式
processedRequest = checkMultipart(request);

checkMultipart(request)源码

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    //判断multipartResolver解析器是否存在 && 请求是否是multipart类型
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        //将请求转换为multipart类型的请求对象,通常为MultipartHttpServletRequest类型
        return this.multipartResolver.resolveMultipart(request);
    }
    return request;
}

2.3、②:根据请求获取 HandlerExecutionChain 对象

//②:根据请求获取HandlerExecutionChain对象
mappedHandler = getHandler(processedRequest);

getHandler(processedRequest)源码如下,遍历所有的处理器映射器HandlerMapping,调用他们的getHandler方法得到能够处理当前请求的HandlerExecutionChain对象,这个对象中包含了 3 个信息

  • handler:请求处理器,通常就是我们自定义的 controller 对象及方法

  • interceptorList:拦截器,当前请求匹配到的拦截器列表

  • interceptorIndex:拦截器索引,用来记录执行到第几个拦截器了

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

有兴趣的可以去看一下RequestMappingHandlerMapping这个类的源码,也是最常用的一个 HandlerMapping,它会根据@RequestMapping来找到能够处当前请求的处理器,RequestMappingHandlerMapping#getHandler 方法查找得到的 HandlerExecutionChain 对象中的 handler 类型为HandlerMethod,代码在下面这个位置

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal

HandlerMethod 对象中包含了能够处理请求的 bean 及方法信息

2.4、③:根据处理器获取 HandlerAdapter

//③:根据处理器获取HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

getHandlerAdapter方法源码,遍历HandlerAdapter列表,找到能够处理当前 handler 的HandlerAdapter,如果没找到会报错

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }

    throw new ServletException("No adapter for handler [" + handler +
                               "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

此方法通常返回的是RequestMappingHandlerAdapter类型的对象,RequestMappingHandlerAdapter这个类会根据HandlerMethod提供的信息,通过反射调用@RequestMapping 标注的方法。

2.5、④:调用拦截器的 preHandle 方法

//④:调用拦截器的preHandle方法,若返回false,处理结束
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}

mappedHandler.applyPreHandle源码如下,主要干了 3 个事情

  • 循环调用拦截器的preHandle方法

  • 如果某个拦截器的preHandle方法返回 false,则反向依次调用那些 preHandle 方法返回 ture 的拦截器的 afterCompletion 方法;这句话有点绕,比如有 3 个拦截器,1、2 的 preHandler 返回了 true,而 3 返回的是 false,那么这里将按照 2、1 的顺序调用他们的 afterCompletion 方法

  • 记录拦截器的执行位置

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        //调用拦截器的preHandle方法
        if (!interceptor.preHandle(request, response, this.handler)) {
            //如果拦截器返回false,则反向依次调用那些preHandle方法返回ture的拦截器的afterCompletion方法
            triggerAfterCompletion(request, response, null);
            return false;
        }
        //记录当前拦截器执行的位置
        this.interceptorIndex = i;
    }
    return true;
}

triggerAfterCompletion方法源码如下,通过拦截器当前执行的位置interceptorIndex逆向调用拦截器的afterCompletion方法

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        try {
            interceptor.afterCompletion(request, response, this.handler, ex);
        }
        catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
    }
}

2.6、⑤:调用 handler 实际处理请求,获取 ModelAndView 对象

2.6.1、过程
//⑤:调用handler实际处理请求,获取ModelAndView对象,这里会调用HandlerAdapter#handle方法处理请求,其内部会调用handler来处理具体的请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

ha.handler 方法内部通通过程会走到RequestMappingHandlerAdapter#invokeHandlerMethod方法,这个方法内部会通过反射调用@RequestMapping 标注的方法,这个方法内部代码比较复杂,咱们就不进去了,这里说一下这个方法主要做了 3 个非常重要的事情:

  • step1:组装目标方法需要的参数

  • step2:通过反射调用处理请求的目标方法,获取方法的返回值

  • step3:对方法的返回值进行处理

下面来细说一下这 3 个步骤,这些地方有好东西,大家集中注意力了。

2.6.2、step1:组装目标方法需要的参数:HandlerMethodArgumentResolver

处理器的方法需要的参数有各种类型的,所以组装这些参数是比较关键的地方,组装参数的源码位于下面这个位置

org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues

获取方法需要的参数值,会用到HandlerMethodArgumentResolver这个对象,叫做:处理器方法参数解析器,用来解析请求,得到方法需要的参数,大家看一下这个接口,源码如下,主要有 2 个方法

  • supportsParameter:是否能够解析 parameter 指定的参数

  • resolveArgument:通过请求和 parameter 参数解析得到参数的值

public interface HandlerMethodArgumentResolver {

 //判断当前解析器是否能处理这个parameter这个参数,也就是说是否能够将请求中的数据转换为parameter指定的参数的值
 boolean supportsParameter(MethodParameter parameter);

 //解析参数:从http请求中解析出控制器需要的参数的值
 Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
   NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

这个接口有很多实现类,列几个比较熟悉的,当大家想知道 springmvc 可以接收哪些类型的参数,以及这些参数有什么特点的时候,可以去看看这些类的源码,你会秒懂的

实现类 对应的控制器参数 说明
PathVariableMapMethodArgumentResolver @PathVariable 标注参数 从 url 中提取参数的值
RequestHeaderMethodArgumentResolver @RequestHeader 标注参数 从 http 头中提取参数值
RequestParamMethodArgumentResolver @RequestParam 标注参数 http 请求参数中获取值
RequestResponseBodyMethodProcessor @RequestBody 标注参数 提取 body 数据,转换为参数类型
ServletResponseMethodArgumentResolver ServletResponse、OutputStream、Writer 这 3 种类型的参数 这几种类型用来控制 http 请求的响应输出流
HttpEntityMethodProcessorHttpEntity HttpEntity 类型的参数 HttpEntity 中包含了 http 请求头和 body 的所有信息
ExpressionValueMethodArgumentResolver @Value 标注的参数 spel 表达式,从 spring 容器中获取值
MapMethodProcessor 参数为 Map 或者子类型 -
ModelMethodProcessor 参数为 org.springframework.ui.Model 或子类型 -
ModelAttributeMethodProcessor @ModelAttribute 标注的参数 -

2.6.3、step2:通过反射调用目标方法

也就是调用 controller 中的@RequestMapping 标注的方法,代码位置

org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle

对应的源码如下,这个方法 springmvc 框架中主要有 2 个地方会调用

  • 第 1 个地方是:调用处理请求的实际方法的时候

  • 第 2 个地方是:方法有异常的时候,异常解析器里面也会用到这个方法,稍后后面会讲

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
                            Object... providedArgs) throws Exception {
    //1.通过反射调用目标方法,内部会组装目标方法需要的参数
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

    //如果返回值为空,表示目标方法中已经完成了请求的所有处理,表示请求处理结束了,将执行mavContainer.setRequestHandled(true)标记请求处理完毕
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    //若getResponseStatusReason()不为空,表示请求已经处理过了
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }
    //走到这里,说明有返回值,标记请求未处理完毕
    mavContainer.setRequestHandled(false);
    //对返回值进行处理
    this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
2.6.4、step3:处理方法返回值:HandlerMethodReturnValueHandler

大家注意,上面代码中这部分代码,如下,会对反射调用的结果 returnValue 进行处理

//对返回值进行处理
this.returnValueHandlers.handleReturnValue(
    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

进入handleReturnValue方法内部去看一下,最终代码在下面这个位置

org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue

这个方法的源码如下

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    //根据返回值找到HandlerMethodReturnValueHandler
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    //调用HandlerMethodReturnValueHandler#handleReturnValue处理返回值
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    //根据返回值判断是否是异步请求
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

这里关键的信息要看HandlerMethodReturnValueHandler接口,这个接口用来处理返回值,看一下其源码,包含 2 个方法

  • supportsReturnType:是否能够处理 returnType 参数指定的返回值

  • handleReturnValue:处理返回值

public interface HandlerMethodReturnValueHandler {

 boolean supportsReturnType(MethodParameter returnType);

 void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
   ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

此接口有很多实现类,如下图,图下的表格中会列出常见的一些及说明,建议大家抽空,都点开看看其源码

实现类 说明
ViewNameMethodReturnValueHandler 返回值为视图名称时的解析器
MapMethodProcessor 返回值为 Map 的解析器
StreamingResponseBodyReturnValueHandler 返回值为 ResponseEntity 类型时的解析器
DeferredResultMethodReturnValueHandler 返回值为 DeferredResult 类型时的解析器,表示异步请求
CallableMethodReturnValueHandler 返回值为 Callable 类型时的解析器,表示异步请求
ModelMethodProcessor 返回值为 Model 类型时的解析器
ModelAndViewMethodReturnValueHandler 返回值为 ModelAndView 类型时的解析器
RequestResponseBodyMethodProcessor 方法上标注有@ResponseBody 注解时返回值的解析器
HttpEntityMethodProcessor 返回值为 HttpEntity 类型但是非 RequestEntity 类型时的解析器

这里找一个比较有代表性的,带大家看一下,就以RequestResponseBodyMethodProcessor来说一下,这个会处理@RequestBody标注的方法,抽取其 2 个关键方法的代码,如下

//判断类上或者目标方法上是否有@ResponseBody注解
@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
            returnType.hasMethodAnnotation(ResponseBody.class));
}

//处理返回值
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
        throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    //1:标注为请求已处理,因为当前handleReturnValue方法会直接将结果输出到客户端,所以后续就不需要再进行视图渲染了,表示请求已经被处理了
    mavContainer.setRequestHandled(true);
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

    //2:将结果输出到客户端
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

上面代码中,这里大家需要注意handleReturnValue方法,这个方法内部会直接将结果输出,后续就没有视图渲染的事情了,所以这里会调用mavContainer.setRequestHandled(true),表示请求已经处理了。

2.7、⑥:调用拦截器的 postHandle 方法

//⑥:调用拦截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);

mappedHandler.applyPostHandle源码如下,逆序调用拦截器的postHandle方法

org.springframework.web.servlet.HandlerExecutionChain#applyPostHandle

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
    throws Exception {

    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        interceptor.postHandle(request, response, this.handler, mv);
    }
}

2.8、⑦:渲染视图

2.8.1、过程
 //⑦:处理分发结果,渲染视图(包含了正常处理和异常情况的处理),将结果输出到客户端
 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

processDispatchResult源码如下

org.springframework.web.servlet.DispatcherServlet#processDispatchResult

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                   @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                   @Nullable Exception exception) throws Exception {
    boolean errorView = false;

    if (exception != null) {
        Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
        //⑦-1:如果有异常,进行全局异常处理
        mv = processHandlerException(request, response, handler, exception);
        errorView = (mv != null);
    }

    if (mv != null && !mv.wasCleared()) {
        //⑦-2:渲染视图
        render(mv, request, response);
        if (errorView) {
            //调用request.removeAttribute方法清理request中错误信息
            WebUtils.clearErrorRequestAttributes(request);
        }
    }

    if (mappedHandler != null) {
        //⑦-3:调用拦截器的afterCompletion方法
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

这个方法主要干了 3 个事情

  • step1:⑦-1:如果有异常,进行全局异常处理

  • step2:⑦-2:渲染视图

  • step3:⑦-3:调用拦截器的 afterCompletion 方法

下面来解析这 3 个步骤

2.8.2、step1:⑦-1:如果有异常,进行全局异常处理
if (exception != null) {
    Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
    //⑦-1:如果有异常,进行全局异常处理
    mv = processHandlerException(request, response, handler, exception);
    errorView = (mv != null);
}

processHandlerException方法源码,主要是遍历异常处理器HandlerExceptionResolverresolveException来处理异常,稍后会说一下这个接口

org.springframework.web.servlet.DispatcherServlet#processHandlerException

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
   @Nullable Object handler, Exception ex) throws Exception {

    // 调用处理器异常解析器解析异常,得到ModelAndView
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            exMv = resolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
    }
    if (exMv != null) {
        //暴露异常信息到request对象中(request.setAttribute)
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }

    throw ex;
}

HandlerExceptionResolver 接口:处理器异常解析器,内部就只有一个方法,用来解析异常的,得到一个 ModelAndView 对象。

public interface HandlerExceptionResolver {

 @Nullable
 ModelAndView resolveException(
   HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}

这个接口有好几个实现类,我们主要关注下ExceptionHandlerExceptionResolver这个类,大家是否还记得注解方式处理全局异常(即使用@ControllerAdvice 和@ExceptionHandler 实现全局异常处理处理),最终这俩注解定义的异常处理会被ExceptionHandlerExceptionResolver这个类进行处理,这个类的源码就不细讲了,比较简单,大家可以去看看,就是一个异常类型匹配处理方法的过程。

2.8.3、step2:⑦-2:渲染视图
//⑦-2:渲染视图
render(mv, request, response);

render方法源码如下

org.springframework.web.servlet.DispatcherServlet#render

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    View view;
    String viewName = mv.getViewName();
    if (viewName != null) {
        //⑦-2-1:调用视图解析器解析视图名称得到视图View对象
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    } else {
        view = mv.getView();
    }

    //⑦-2-2:调用视图的render方法渲染视图,将结果输出到客户端
    view.render(mv.getModelInternal(), request, response);
}

此方法干了 2 件事

  • ⑦-2-1:调用视图解析器解析视图名称得到视图 View 对象

  • ⑦-2-2:调用视图的 render 方法渲染视图,将结果输出到客户端

下面进去细看一下

⑦-2-1:调用视图解析器解析视图名称得到视图 View 对象
//⑦-2-1:调用视图解析器解析视图名称得到视图View对象
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

resolveViewName方法源码如下,遍历视图解析器,解析视图名称,得到视图对象 View

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
   Locale locale, HttpServletRequest request) throws Exception {

    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}
⑦-2-2:调用视图的 render 方法渲染视图,将结果输出到客户端
//⑦-2-2:调用视图的render方法渲染视图,将结果输出到客户端
view.render(mv.getModelInternal(), request, response);

这里我们以 InternalResourceView 为例,进到其 render 方法中,看看里面干了什么,最终会进到其renderMergedOutputModel方法中,源码如下,这里代码就非常亲切了,不多解释,看注释

protected void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    // 将model中的数据遍历后放在request中(request.setAttribute(name,value))
    exposeModelAsRequestAttributes(model, request);

    // 获取跳转的页面的路径
    String dispatcherPath = prepareForRendering(request, response);

    // 调用request.getRequestDispatcher(path)得到RequestDispatcher对象
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);

    //实现页面跳转
    if (useInclude(request, response)) {
        rd.include(request, response);
    }else {
        rd.forward(request, response);
    }
}
2.8.3、step3:⑦-3:调用拦截器的 afterCompletion 方法
⑦-3:调用拦截器的afterCompletion方法
mappedHandler.triggerAfterCompletion(request, response, null);

mappedHandler.triggerAfterCompletion方法的源码如下,反向调用拦截器的afterCompletion方法

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        try {
            interceptor.afterCompletion(request, response, this.handler, ex);
        }
        catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
    }
}

过程到这里就结束了,需要大家结合源码多看几遍,还是比较容易的。

3、处理流程:纯文字描述

1、用户向服务器发送请求,请求被 SpringMVC 前端控制器 DispatcherServlet 捕获

2、DispatcherServlet 根据该 URI,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以 HandlerExecutionChain 执行链对象的形式返回

4、DispatcherServlet 根据获得的 Handler,选择一个合适的 HandlerAdapter

5、如果成功获得 HandlerAdapter,此时将开始执行拦截器的 preHandler(…)方法【正向】

6、提取 Request 中的模型数据,填充 Handler 入参,开始执行 Handler(Controller)方法,处理请求,在填充 Handler 的入参过程中,根据你的配置,Spring 将帮你做一些额外的工作:

  1. HttpMessageConveter:将请求消息(如 Json、xml 等数据)转换成一个对象,将对象转换为指定的类型信息

  2. 数据转换:对请求消息进行数据转换。如 String 转换成 Integer、Double 等

  3. 数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等

  4. 数据验证:验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或 Error 中

7、Handler 执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象。

8、此时将开始执行拦截器的 postHandle(...)方法【逆向】

9、根据返回的 ModelAndView(此时会判断是否存在异常:如果存在异常,则执行 HandlerExceptionResolver 进行异常处理)选择一个适合的 ViewResolver 进行视图解析,根据 Model 和 View,来渲染视图

10、渲染视图完毕执行拦截器的 afterCompletion(…)方法【逆向】

11、将渲染结果返回给客户端

4、小结

本文东西比较多,建议大家抽空结合源码多看几遍,下一篇文章将通过源码介绍 springmvc 容器的启动过程,干货也是满满的,敬请期待。

5、案例代码

git地址:https://gitee.com/javacode2018/springmvc-series

6、SpringMVC 系列

  1. SpringMVC 系列第 1 篇:helloword

  2. SpringMVC 系列第 2 篇:@Controller、@RequestMapping

  3. SpringMVC 系列第 3 篇:异常高效的一款接口测试利器

  4. SpringMVC 系列第 4 篇:controller 常见的接收参数的方式

  5. SpringMVC 系列第 5 篇:@RequestBody 大解密,说点你不知道的

  6. SpringMVC 系列第 6 篇:上传文件的 4 种方式,你都会么?

  7. SpringMVC 系列第 7 篇:SpringMVC 返回视图常见的 5 种方式,你会几种?

  8. SpringMVC 系列第 8 篇:返回 json & 通用返回值设计

  9. SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思?

  10. SpringMVC 系列第 10 篇:异步处理

  11. SpringMVC 系列第 11 篇:集成静态资源

  12. SpringMVC 系列第 12 篇:拦截器

  13. SpringMVC 系列第 13 篇:统一异常处理

  14. SpringMVC 系列第 14 篇:实战篇:通用返回值 & 异常处理设计

  15. SpringMVC 系列第 15 篇:全注解的方式  &  原理解析

7、更多好文章

  1. Spring 高手系列(共 56 篇)

  2. Java 高并发系列(共 34 篇)

  3. MySql 高手系列(共 27 篇)

  4. Maven 高手系列(共 10 篇)

  5. Mybatis 系列(共 12 篇)

  6. 聊聊 db 和缓存一致性常见的实现方式

  7. 接口幂等性这么重要,它是什么?怎么实现?

  8. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

8、【路人甲 Java】所有系列高清 PDF

领取方式,扫码发送:yyds

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/likun557/article/details/120299993

智能推荐

SAP FIORI开发入门-徐春波-专题视频课程-程序员宅基地

文章浏览阅读1.4k次,点赞3次,收藏8次。【课程目标】打造一个简单实用的 SAP FIORI 入门开发课程,帮助广大 SAP 技术人员或者希望进入 SAP 技术领域的人打开一扇门。【课程形式】视频教程 + PDF 参考资料【学习门槛】零门槛,无需任何额外知识【作者微信】eksbobo【如何入群】使用购买课程的 ID 作为请求信息,发送到作者的微信添加好友,作者会把您拉入到这门课程的微信群中。..._sap fiori开发视频教程--由浅入深学习fiori开发

启发式合并(dsu),树上启发式合并(dsu on tree)总结-程序员宅基地

文章浏览阅读232次。启发式合并(dsu),树上启发式合并(dsu on tree)总结_启发式合并

工具方法:一次性将对象中所有null字段,转为空字符串_null值转换为空字符串-程序员宅基地

文章浏览阅读9.1k次,点赞12次,收藏30次。当我们的 Java 对象在响应前端,或者在做数据导出的时候,我们并不希望将对象中为 null 的属性值直接返回给前端,不然显示或导出的就是一个 null ,这样对用户不是很友好。如果我们一个个字段的去处理,这样不但增加了人力,而且使得代码中逻辑冗余,显得不够优雅。于是下面我写了一个通用方法:将对象中的 String 类型属性的null 值转换为空字符串的方法,具体代码如下:/** * 把对象中的 String 类型的null字段,转换为空字符串 * * @param <.._null值转换为空字符串

2023最新版kali安装教程_kali下载-程序员宅基地

文章浏览阅读1.7w次,点赞45次,收藏202次。2023最新版kali安装教程打开kali官网(https://www.kali.org/),下载kali镜像_kali下载

nest.js实战之集成sentry_sentry nestjs-程序员宅基地

文章浏览阅读581次。1.简介Sentry API用于将事件提交给Sentry收集器以及导出和管理数据。报告和Web API分别进行了版本控制。2.安装仓库:https://github.com/ntegral/nestjs-sentrynpm install --save @ntegral/nestjs-sentry @sentry/node@ntegral/nestjs-sentry:nestjs的sentry模块,提供了一些可注入服务 @sentry/node:sentry..._sentry nestjs

2024最全前端面试系列(CSS)(盒模型、flex)(1),前端面试题刷题-程序员宅基地

文章浏览阅读814次,点赞24次,收藏27次。定位以外的第一个父元素进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。大前端和全栈是以后前端的一个趋势,懂后端的前端,懂各端的前端更加具有竞争力,以后可以往这个方向靠拢。生成绝对定位的元素,相对于。生成相对定位的元素,相对于其。

随便推点

什么是人工智能(AI)?—— 你需要知道的三件事-程序员宅基地

文章浏览阅读1.1k次,点赞16次,收藏25次。人工智能 (AI) 是对人类智慧行为的仿真。它通常是设计用来感知环境、了解行为并采取行动的一台计算机或一个系统。想想自动驾驶汽车:此类 AI 驱动系统将机器学习和深度学习等 AI 算法集成到支持自动化技术的复杂环境。据麦肯锡预计,到 2030 年,AI 的全球经济价值将高达 13 万亿美元。这是因为在 AI 浪潮的影响下,几乎各行各业乃至每一个应用领域的工程环节都在转型。除了自动驾驶以外,AI 还广泛应用于以下领域:机器故障预测模型,告知何时需要进行机器保养;健康和传感器分析,如病患监护系统;

VueRouter(vue-router 路由)最全笔记,实战实用,通俗易懂_router及vue-router教程-程序员宅基地

文章浏览阅读1.8k次,点赞3次,收藏8次。VueRouter安装和使用vue-router安装模块化中使用使用vue-router的步骤使用history模式router-link重定向/默认路由点击事件跳转路由动态路由路由懒加载路由嵌套参数传递(一)路由元信息全局导航守卫前置全局后置钩子组件内守卫路由独享的守卫keep-alive注意URL:协议://主机:端口/路径?查询(query)所有的组件都继承自Vue类的原型打包:npm run buildredirect:[ˌriːdəˈrekt ] 重定向replace:没有返回箭头_router及vue-router教程

运维——1.网线接在家用无线路由器LAN口依然可以上网,什么原理_路由器为啥插lan口为什么还能上网-程序员宅基地

文章浏览阅读641次。这种连接方式实际上是将路由器作为一个普通的网络交换机来使用_路由器为啥插lan口为什么还能上网

element-ui的el-upload上传图片自定义请求和vue-quill-editor富文本结合使用_vue中使用el-upload自定义editor-程序员宅基地

文章浏览阅读293次。vue-quill-editor默认的图片插入方式,是直接将图片转成base64编码,这样的结果是整个富文本的html片段十分冗余。我们的服务器端接收的post的数据大小都是有限制的,这样的话导致提交失败,就算不提交失败,大量的数据存入数据库也不是好事。为了解决这个问题,我考虑了两个方案,换一个富文本编辑框框架,另一个是修改vue-quill-editor的框架代码。..._vue中使用el-upload自定义editor

HTML/CSS常见的三种水平居中方式(好文备忘)_htmlcss水平居中-程序员宅基地

文章浏览阅读125次。HTML/CSS常见的三种水平居中方式_htmlcss水平居中

Python 和 Tensorflow 相关_add_weight-程序员宅基地

文章浏览阅读315次。文章目录pipTensorflow 2.1.0tf.kerasKeras框架中的epoch、bacth、batch size、iterationtf.keras.layerstf.keras.layers.Dense 添加一个全连接层tf.keras.layers.Conv2D 添加一个卷积层TensorboardObject LocalizationOthors激活函数model.evaluate 和 model.predict 的区别TODOTODOpippip 镜像pip install -_add_weight