进入 DispatchServlet 的“身体”

前言

springmvc 是一个标准的 MVC Web 层框架,由一个前端控制器 DispatchServlet(实际上是一个标准的继承自 HttpServlet 的Servlet)进行总调度,Model 在 Controller 与 View 中进行数据的传输。如果当前请求是页面请求的话则转发到 VIew 中进行处理,如果是一个二进制请求的话,则直接返回 response 。

springmvc 架构.png

与其他 web 框架不一样的是,DispatchServlet 被充分集成在了 spring IOC 容器中。因此它具有了所有 spring 所具有的特点,可以非常简单的做到对控制器中所有 bean 的注册与管理。并且可以在整个运行流程中及其方便的对所有已注册了的 bean 进行访问。每个 DispatchServlet 都有自己单独的注册中心,称为 WebApplicationContext 。它可以由两部分组成,一个单独的 Servlet WebApplicationContext 和其所继承的 Root WebApplicationContext。当要使用一个 bean 时,如果 Servlet WebApplicationContext 中没有进行注册,便去 Root WebApplicationContext 中进行查找。

TypliyRoot.png

这两个 WebApplication 便是我们经常所在项目中所配置的两个 applicationContext 。不过这种继承机制也有缺陷,可能会因为某些疏忽导致 bean 的重复注册或缺漏注册,比如 component-scan ,如果两者都配置了,当中间产生了交集时便会产生重复注册,因此 spring 也提供了另外一种实现方式。

SingleRoot.png

这种方式中只有一个 Root WebApplicationContext,可以有效的避免因继承机制而产生的问题。这种情况下的配置:

<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value></param-value>
            </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

与 spring IOC 容器进行集成带来的便利不仅仅只是 bean 的访问方便,更大的优势是扩展的方便。当我们我对其中某个 bean 进行功能的扩展时,只需要自定义一个 bean,然后在 WebApplication 中进行注册便可以完成功能的扩展。

从 Service 到 Service

既然 DispatchServlet 是一个标准的 Servlet ,那么肯定就有 service 方法。service 方法便是前端控制器的入口,为了探究内部具体的执行流程,我们接下来新建一个项目,通过 debug 的信息来完整的探讨一遍 DispatchServlet 的整体执行流程,看一下从调用 Service 到执行完 Service,这中间所经过的一些主要步骤。通过这篇文章,你可以明白以下几点:

  • HandlerMapping 与 HandlerAdapter 的作用与执行时机
  • interceptor 的整体执行流程以及它的调用流程
  • DispatchServlet 是如何通过 request 找到它所对应的处理方法
  • Controller 中的方法是如何进行参数的注入的,以及其值的来源
  • @ResponseBody 为什么能够返回 Json 数据,以及是如何处理的
  • Controller 中方法返回值的具体处理流程,Dispatch 是如何通过返回的 viewName 去寻找具体的 view 的

工程目录

工程目录.png

这是一个很普通的 web 项目目录,只有 web 层,三个类,一个 controller 类,一个 pojo 类,一个 interceptor 。springMvc.xml 中的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- @Controller 扫描 -->
    <context:component-scan base-package="info.lmovse.explore" />

    <!-- 注解驱动: 作用:替我们自动配置最新版的注解的处理器映射器和处理器适配器 -->
    <mvc:annotation-driven />

    <!-- 初始化拦截器 -->
    <bean id="exploreInterceptor" class="info.lmovse.explore.ExploreInterceptor"/>

    <!-- 针对路径配置拦截器 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <ref bean="exploreInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

这里注册了 interceptor 匹配所有请求。接下来打开 controller,这里有个 explore 方法,它接收三个参数,一个 request,两个普通的 String 参数,并且,它被标注了 @ResponseBody 注解,表明要返回的是 Json 数据。我们在 explore 方法中打个断点,启动项目。

explore 方法.png

方法调用栈

项目启动后,进入首页,这是一个表单,进行一些简单的输入后点提交成功进入到 explore 方法中的断点,参数的值也成功的注入了。此时已经完成了 DispatchServlet 的 input 流程,具体是如何调用的呢?我们不可能打开 DispatchServlet 的源码然后按照自己的理解去打断点去调试,这样不仅浪费时间,而且很难完整的找出所有的 input 流程。我们可以打开 debugger 的方法调用栈,在这里可以很清晰的看到 input 流程中所调用的主要方法,通过这些方法,我们便可以一步步的梳理出整个的 input 流程。

首页输入数据,点击提交

首页

进入断点。

进入断点.png

打开方法调用栈,可以看到这里完整的显示出来从 request 进来到找到 explore 方法所调用的所有还未出栈方法。

方法调用栈.png

FrameWorkServlet

从方法的调用栈信息我们可以看到,前面是 Tomcat 的一些方法的调用,并且看到熟悉的 HttpServletservice 方法,而且首先调用的并不是 DispatchServlet ,是一个叫做 FrameWorkServlet 的类,它是 DispatchServlet 的父类,是 springmvc 的基础 Servlet,在这里进行的主要工作就是对 WebApplicationContext 实例的管理,包括通过配置文件去创建一个实例。我们在 web.xml 中进行的所有配置都是在这里进行处理的。具体对 request 的处理就由它的子类 DispatchServlet 完成。

接着调用它的 doPost 方法,因为这是一个表单的 post 请求,不过其内部就是一些对 bean 的初始化处理,然后再调用子类对 doService 方法的实现来进行处理。

doPost.png
processRequest.png

doService()

到了这里,便开始真正的对 request 进行处理了,由于需要对一些执行过程的查看,我们需要实时断点去观察其中一些值的变化,因此需要对这些调用栈中的方法都打上断点,一个一个去执行,打上断点,重新启动,输入表单,提交,成功进入 doService 方法。

doService.png

在这个方法里,主要完成以下两个功能

  • 保存 request 属性的快照,使在后续处理中可以恢复原来的数据,并将一些框架的对象注入到 request 中使处理器对象或者视图对象可以使用
// 封装框架的一些内置对象到 request 域中
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
  • 注入通过 redirect 传递来的( RedirectAttribute) 属性。
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
    request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

然后调用 doDispatch 进行请求的分发

执行 doDispatch.png

doDispatch():

这个单词的意义也表明这个方法的意义。它是请求的处理分发中心,不管是什么类型的 request 都在这里进行处理。在这里可以获取请求所对应的 controller 方法,可以获取它的请求映射器与请求适配器,还可以对请求进行 interceptor 的处理,它本身没有具体的处理方法,都是通过调用本类的其它方法或者其它类的方法来进行具体的处理,然后获取最终的处理结果。

doDispatch.png
  • 判断当前请求是否是文件请求,如果是的话就将请求转换为文件请求,并用文件请求解析器解析请求 。解析完后返回被转换为文件请求的请求对象。
processedRequest = checkMultipart(request); 
    => true ? return this.multipartResolver.resolveMultipart(request) : return request;
// 标记当前请求是否被解析为文件请求,是的话处理完请求后清理文件资源 
multipartRequestParsed = (processedRequest != request);
    ...
    => true ? cleanupMultipart(processedRequest);
  • 通过配置的 HandlerMappingRequestMappingHandlerMapping) 对象来对 request 进行处理,获取请求所对应的mappedHandler(即具体处理请求的 method 方法,实际上会最终被包装成一个HandlerExecutionChain 对象),其原理是将路径名与所有已注册的路径 map 集合进行匹配,先找出一类路径,再将这类路径进行排序,找到 一个最佳匹配路径,随后获取该路径对应的方法。同时也会将注册了对应 URL 的过滤器挂载到该 chain 中,形成一条方法执行链。返回该 chain。(Tips: chain 中可以注入任意类型的 handler 对象,以适配其他框架的 handler 对象)。

HandlerMethod

是一种对 Method 进行封装的对象,其内部通过传入 一个 bean 和一个 method ,并对其进行一些封装,使其能够便利的获取方法参数,方法返回值,以及方法注解等信息。

Provides convenient access to method parameters, the method return value, method annotations

HandlerExecutionChain

是一种对 handler 的链式封装,可在其中挂载单个 handler 以及多个针对该 handler 的 intercepters。

Handler execution chain, consisting of handler object and any handler interceptors.

通过 hm 获取 handler ,具体处理流程过于繁杂,不便截图,在下面有简单的伪代码表示

getHandler.png

在这里可以看到当前环境有两个映射器,三个处理器。怎么来的呢?其中 ReqMappingHandlerMappingRequestMappingHandlerAdapter 是因为我们在配置文件中配置了 <mvc:annotation-driven /> 所产生。而其他三个则是系统给的默认值。

getHandlerDebugger.png

获取 handlerMethod 的伪代码:

// getHandler 方法通过 request 获取一个与请求路径最佳匹配的 method ,并将其包装为 HandlerMethod。
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    => List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    => addMatchingMappings(directPathMatches, matches, request);
    => Collections.sort(matches, comparator);
    => Match bestMatch = matches.get(0);
    => return bestMatch.handlerMethod;
  • 获取到 mappedHandler后,为该 handler 配置 HandlerAdapter 对象(即我们所配置的RequestMappingHandlerAdapter
  • 判断该请求是否是支持缓存的 GET 对象,是的话通过判断请求头的 lastModified 属性来判断是否有修改,没有的话直接返回。
handlerModified.png
  • 执行请求过滤器的 preHandler 方法,返回 TRUE 时继续向下执行,返回 FALSE 代表请求被过滤了,这时直接返回。
doHandler.png

执行完后才开始真正的开始处理与调用 handler。

handle():

  • 调用子类的 handlerInternal 方法来进行 handler的具体细化处理,并在这里又将 HandlerExecutionChain 强转回 HandlerMethod 对象,用于后续处理。
handlerIn.png

handleInternal()

这个方法的主要作用就是对当前请求进行一些判断与处理,可以直接跳过,进入下一个方法。

handler.png
  • 判断当前 request 的 method 是否被所配置的 adapter 所支持,以及当前 adapter 是否配置了 requireSession

    和该 request 是否携带有 session,检查不通过抛出运行时异常

// Check whether we should support the request method.
String method = request.getMethod();
    if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
        throw new HttpRequestMethodNotSupportedException(
            method, StringUtils.toStringArray(this.supportedMethods));
    }

// Check whether a session is required.
if (this.requireSession && request.getSession(false) == null) {
    throw new HttpSessionRequiredException("Pre-existing session required but none found");
}
  • 判断是否开启了 session 锁,开启了话就在 session 锁中执行 handlerMethod 。
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
    HttpSession session = request.getSession(false);
    if (session != null) {
        Object mutex = WebUtils.getSessionMutex(session);
        synchronized (mutex) {
            return invokeHandlerMethod(request, response, handlerMethod);
        }
    }
}
  • 调用 invokeHandlerMethod() 来调用处理器的方法
invokeHandlerMethod(request, response, handlerMethod);

invokeHandlerMethod()

这个方法是个很重要的方法,在这里进行了大部分后续操作必须的初始化过程。首先创建了两个重要的对象,分别为 ServletInvocableHandlerMethodModelAndViewContainer 。其中,ServletInvocableHandlerMethod 不仅可以通过继承获得对方法参数进行解析的能力,还可以通过注册一个 HandlerMethodReturnValueHandler 使其可以对返回值进行处理。 同时它也支持通过方法级别的注解 @ResponseStatus设置响应的状态码,如 404 ,500 等。

invokeAndHandlerMethod.png

ServletInvocableHandlerMethod => InvocableHandlerMethod => HandlerMethod

InvocableHandlerMethod

Provides a method for invoking the handler method for a given request after resolving its method argument values through registered {@link HandlerMethodArgumentResolver}s.

提供了一个可实际调用的 method,该 method 的参数通过注册了的方法参数解析器集合遍历解析匹配的值后后进行值的注入。同时我们自己进行配置的 converter 最终也会被注册进方法参数解析器集合中

ServletInvocableHandlerMethod

Extends InvocableHandlerMethod with the ability to handle return values through a registered {@link HandlerMethodReturnValueHandler}s and also supports setting the response status based on a method-level
@ResponseStatus annotation.

继承自InvocableHandlerMethod ,并通过注册一系列的 HandlerMethodReturnValueHandler 使其有能力处理 handler 的返回值,支持通过方法级别的注解 @ResponseStatus 注解来设置响应的状态码。`

在这里,我们可以很清楚的看到这个方法是如何做到对 handler 的参数注入及返回值的解析处理了。它是通过 ServletInvocableHandlerMethod类中注册的 argumentResolversreturnValueHandlers 这两个解析器集合来解析参数及其方法执行完后的返回值的。

argumentResolversreturnValueHandlers 它们两个所属类的父接口分别为 HandlerMethodArgumentResolverHandlerMethodReturnValueHandler 。这是 3.1 版本新增的两个接口,与 RequestMappingHandlerAdapter 一起出现的,用于替代之前的 AnnotionMethodHandlerAdatper,那么在 3.1 之前是怎么处理的?else if else if else if else if .................. 大量的 else if 用来判断参数类型,导致 AnnotionMethodHandlerAdatper 类及其臃肿。为了解决这种情况,从 spring 3.1 开始,引入了 HandlerMethodArgumentResolverHandlerMethodReturnValueHandler 这两个策略接口来分开处理,这种解决方式便是用的设计模式中的一种 —— 策略模式。

ModelAndViewContainer 这个类的可从字面意思简单的理解为 ModelAndView 对象的容器对象。它包含一些在创建 ModeAndView 对象时的一些描述信息以及 Model 实例。在这里我们可以看到它注入了 request 中的 inputFlashMap 属性,假如这个请求是通过 redirect 过来的话,且用了 RedirectAttribute 类来传递值,那么在这里就可以将其值通过 initModel 方法注入到当前的 ModelAttribute 中,然后在方法中可以通过 ModelAndAttribute 参数获取值(能且只能通过 ModelAttribute 类获取,因为注入的就是 ModelAttribute 类)。

最后调用 invokeAndHandle 方法进行处理,这里的 invokeAndHandler 方法属于其父类 InvocableHandlerMethod 中的方法

doInvokeHandler.png

invokeAndHandle()

调用 invokeForRequest 方法继续进行处理并返回 returnValue,这里并不是方法的终点,因此在执行完后还会执行到这个地方,我们继续在 setResponseStatus 这个方法打个断点。

invokeForRequest.png

invokeForRequest()

在这里,首先通过 getMethodArgumentValues 获取 handler 的参数值集合。随后,遍历参数对象,找到与每个参数所匹配的 HandlerMethodArgumentResolver 来解析该参数。值得注意的是,遍历过程中如果找到了合适的解析器,但是没有成功注入值时会抛出运行时异常。一个典型的例子便是 @RequestParam 注解,如果你加了这个注解,那么参数名必须与表单名一致,否则会报错。但如果你不加,不一致时不会报错,而且适配 @RequstParam 的解析器同样能够解析没有 @RequestParam 注解的 param 字段。

// providerArgs 在此时还是空值,为进行参数值的注入而初始化的一个参数数组
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    =>
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
     
        // getMethodParameters 这个方法是 HandlerMethod 中的方法,在 handlermethod 中对参数进行处理,并将其封           装成一个 MethodParameter 数组返回。
        MethodParameter[] parameters = getMethodParameters();
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            // 该行代码只为 debug 时使用,无具体意义,可忽略。
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            if (this.argumentResolvers.supportsParameter(parameter)) {
                try {
                    args[i] = this.argumentResolvers.resolveArgument(
                            parameter, mavContainer, request, this.dataBinderFactory);
                    continue;
                }
                catch (Exception ex) {
                    throw ex;
                }
            }
            if (args[i] == null) {
                String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
                throw new IllegalStateException(msg);
            }
        }
        return args;
    }

MethodParameter

这是一个辅助类,该类对 parameter 进行了一些描述信息的封装,例如它在参数列表中的索引位置,或者它的嵌套泛型类的索引位置。利用该辅助类,我们可以轻松的获取 methodparamter 的一些信息。

Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method} or {@link Constructor} plus a parameter index and a nested type index for a declared generic type. Useful as a specification object to pass along.

当所有的参数数组都进行了值的注入后,便执行 doInvoke() 方法。

doInvoke()

这里只有一行代码,通过 getBean 方法获取到当初传入的 handler(即 controller 中具体要执行的方法),然后传入具体的参数值数组通过反射完成了值的注入。

getBridgedMethod().invoke(getBean(), args);

explore()

这里进行具体业务的处理,为了测试,只简单地打印出来参数值,并返回一个 User 对象。

@RequestMapping("/explore")
@ResponseBody
public User explore(String username, String password) {
   System.out.println(username);
   System.out.println(password)
   User user = new User();
   user.setUsername(username);    
   user.setPassword(password);
   return user;
}

在这里,对方法设置了 @ResponseBody 的注解,表示要对返回对象进行 Restful 处理。handler 方法运行完后,重新进入

invokeAndHandle 方法。

invokeAndHandle()

重新回到这里。这里先对 responsestatus 进行了处理,处理后再对 returnValue 进行非空判断,里面都是一些特殊情况的判断,可以直接跳过,直接看最后一行代码,调用了 this.returnValueHandlers.handleReturnValue 方法。

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
    this.setResponseStatus(webRequest);
    if(returnValue == null) {
        if(this.isRequestNotModified(webRequest) || this.hasResponseStatus() || mavContainer.isRequestHandled()) {
            mavContainer.setRequestHandled(true);
            return;
        }
    } else if(StringUtils.hasText(this.responseReason)) {
        mavContainer.setRequestHandled(true);
        return;
    }
    mavContainer.setRequestHandled(false);
    this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
    
}

这里先调用了 selectHandler 方法进行针对该 returnValuereturnValueHandler 的选取。在 selectHandler 方法中,就是一个简单的 forEach 循环判断迭代的 handler 是否支持该返回类型,由于我们加了 @ResponseBody 注解。因此,毫无疑问最终会选取能处理该注解类型以及返回类型的 handler,这个 handler 的类就是当时注入的 ReturnValueHandler 集合中的 RequestResponseBodyMethodProcessor

public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
        Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }

    private HandlerMethodReturnValueHandler selectHandler(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;
    }

我们点进 RequestResponseBodyMethodProcessor 中看一下,可以很清楚的看到该类就是通过获取方法的 @ResponseBody 注解来判断是否支持 returnType 的,同时我们通过类名以及另外一个构造函数可以看到,当我们在方法参数中使用 @RequestBody 注解时,也是使用这个 handler 进行处理的。

public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
}

public boolean supportsReturnType(MethodParameter returnType) {
        return AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) !=                   null || returnType.getMethodAnnotation(ResponseBody.class) != null;
 }

那么它是怎么处理的?如下,先将当前 request 标记为已处理状态,然后通过一个叫做 writeWithMessageConverters 的方法进行处理,该方法并不是它本身的方法,而是从父类 AbstractMessageConverterMethodProcessor 中继承而来的,因此我们要点进父类看具体的处理流程。

public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        mavContainer.setRequestHandled(true);
        this.writeWithMessageConverters(returnValue, returnType, webRequest);
}

AbstractMessageConverterMethodProcessor 中,首先通过 writeWithMessageConverters 方法生成 ServletServerHttpRequestServletServerHttpResponse 对象,这两个对象从名字就能看得出来是继承或者实现自哪个两个类了。继续跟着 this.writeWithMessageConverters(...)

protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
        this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

接下来是个很长的方法,有将近 100 来行代码,但其要做的事情就两件:

  • 找到用户所配置的 response contentType,并与请求所支持的 response contentType 进行对比,找出两者都支持的 contentType 作为返回的类型。
  • 选择合适的 HttpMessagerConverter 对象来对返回值进行 json 化,并将其写入 responseBody 中。当在 springmvc 的配置文件中配了 <mvc: annotion-driven> 时,这个 converter 默认就是 AbstractJackson2HttpMessageConverter
// 获取用户所配置的 producibleMediaTypes 与 Request 所支持的 requestedMediaTypes,接下来便是遍历判断选择了 
List<MediaType> requestedMediaTypes = this.getAcceptableMediaTypes(servletRequest);
List<MediaType> producibleMediaTypes = this.getProducibleMediaTypes(servletRequest, returnValueClass, returnValueType);

// 取出 HttpMessageConverter 集合中的第一个迭代对象,进行一些合法性检查后便将 returnValue、     selectedMediaType、outputMessage 传入 write() 中进行写入。
HttpMessageConverter<?> messageConverter = (HttpMessageConverter)var13.next();
   if(messageConverter instanceof GenericHttpMessageConverter) {
        if(((GenericHttpMessageConverter)messageConverter).canWrite(returnValueType, returnValueClass, selectedMediaType)) {
                         returnValue = this.getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType, messageConverter.getClass(), inputMessage, outputMessage);
                         if(returnValue != null) {
                                this.addContentDispositionHeader(inputMessage, outputMessage);
                                ((GenericHttpMessageConverter)messageConverter).write(returnValue, returnValueType, selectedMediaType, outputMessage);
               
                                }

                                return;
                     }
   }

有关 HttpMessageConverter 的相关知识或者写入的具体流程可以看这篇博客。执行到这里,主要的步骤便执行完了,接下来便是一下细节的处理,return 后,直接返回到 invokeHandlerMethod 方法。

invokeHandlerMethod

invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
return asyncManager.isConcurrentHandlingStarted()?null:this.getModelAndView(mavContainer, modelFactory, webRequest);

在这里,直接返回的便是 ModelAndView 对象,进入 getModelAndView 方法,我们可以看到这样一段代码:

ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
if(!mavContainer.isViewReference()) {
      mav.setView((View)mavContainer.getView());
}
if(model instanceof RedirectAttributes) {
      Map<String, ?> flashAttributes = ((RedirectAttributes)model).getFlashAttributes();
      HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class);
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
 }
 return mav;

如先前所说,ModelAndViewContainer 就是 ModelAndView 的容器,通过它来生成具体的 ModelAndView 。 这里先判断是否包含有 view ,有 view 的话设置 view,然后判断是否有 RedirectAttributes ,有的话通过 mavContainer 获取 flashAttributes ,并将其放入到 requestoutputFlashMap 中。

这里也执行完后,我们便又重新回到 doDispatch 方法了。

doDispatch()

处理完 handler 后,接下来便是调用请求拦截器的 postHandle 方法,进行后置拦截器的拦截,然后是处理 result 。我们知道,拦截器有三个方法,最后一个 afterCompetition 方法是要在视图返回后调用,那么在 processDispatchResult 方法中肯定有返回视图与调用requestafterCompetition 两个方法。点进里面查看。

// Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

其他的直接忽略,两个关键方法:render(mv, request, responsed)mappedHandler.triggerAfterCompletion,见方法名知其义,具体实现就不进去看了。

postHandler.png

处理完后,重新回到 doService => doPost => processRequest => service

至此,整个调用过程结束。

这里只介绍了返回 Json 对象的一种情况,但也是特殊情况的一种,其他的都大同小异。

接下来是一张图来描述一下整体的调用流程

springmvc 执行流程图解.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,800评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,500评论 18 399
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,670评论 0 9
  • 阿猫不是猫,是我们给小侄女的别号。 阿猫的妈妈是我们兄弟姊妹中最小的,当时父母本来是没有准备再要孩子的,但母亲意外...
    千禾随笔阅读 1,202评论 27 42