万字长文,深度解析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

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include&lt;stdio.h&gt;#include&lt;string.h&gt;#include&lt;stdlib.h&gt;#include&lt;malloc.h&gt;#include&lt;iostream&gt;#include&lt;stack&gt;#include&lt;queue&gt;using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签