前言

上一节我们看到了SpringMVC在初始化的时候做了大量的准备工作,本章节就重点跟踪下SpringMVC的实际调用过程。

1、DispatcherServlet

记不记得,在大学期间或者刚接触Java WEB开发的时候,前后端交互往往需要一个Servlet来接收请求,并返回信息。时至今日,Servlet仍不过时。

如果是一个SpringMVC的项目,在WEB.XML里面需要配置一个DispatcherServlet,它本质就是个Servlet。或许我们还有印象,在请求到达的时候,就会调用到Servlet的service方法。那么,说回到SpringMVC,就是FrameworkServlet类的service方法。

    protected void service(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        String method = request.getMethod();        if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {            processRequest(request, response);        }        else {            super.service(request, response);        }    }

经过一系列的判断匹配,最后调用到DispatcherServlet类的doService方法。可以看到的是,doService方法向request里面添加了很多默认的属性,如果需要,我们就可以拿到。并在最后调用了实际的处理方法,doDispatch()。

  1. protected void doService(HttpServletRequest request, HttpServletResponse response) {

  2.    // Make framework objects available to handlers and view objects.

  3.    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());

  4.    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);

  5.    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);

  6.    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());


  7.    FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);

  8.    if (inputFlashMap != null) {

  9.        request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));

  10.    }

  11.    request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());

  12.    request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);


  13.    doDispatch(request, response);

  14. }

2、doDispatch

实际处理的时候,大致分为几个步骤。

  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {

  2.    HttpServletRequest processedRequest = request;

  3.    HandlerExecutionChain mappedHandler = null;

  4.    boolean multipartRequestParsed = false;

  5.    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

  6.    try {

  7.        ModelAndView mv = null;

  8.        Exception dispatchException = null;

  9.        try {

  10.            processedRequest = checkMultipart(request);

  11.            multipartRequestParsed = (processedRequest != request);

  12.            //第一步 确定请求的处理器

  13.            mappedHandler = getHandler(processedRequest);

  14.            if (mappedHandler == null || mappedHandler.getHandler() == null) {

  15.                //URL没有找到匹配项,返回404

  16.                noHandlerFound(processedRequest, response);

  17.                return;

  18.            }

  19.            // 第二步 确定请求的处理适配器

  20.            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());


  21.            //第三步 调用拦截器 (方法调用前执行)

  22.            if (!mappedHandler.applyPreHandle(processedRequest, response)) {

  23.                return;

  24.            }

  25.            // 第四步  方法调用

  26.            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

  27.            //第五步 调用拦截器  (方法调用后执行)

  28.            mappedHandler.applyPostHandle(processedRequest, response, mv);

  29.        }

  30.        //第六步 处理方法返回 (返回页面或者属性)

  31.        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

  32.        //第七步 调用拦截器(请求完成后执行)

  33.        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);

  34.    }

  35. }

不着急,下面我们一个一个的来看。

2.1、 获取处理器 getHandler

获取处理器,就是根据请求的uri,匹配到Method方法。具体做法,我们在上一节Spring源码分析(四)SpringMVC初始化做了预估,实际上Spring也确实是这样做的。 根据uri获取handlerMapping,再以handlerMapping为key,从handlerMethods容器中拿到相应的HandlerMethod对象。不过,它把HandlerMethod对象做了两次封装,源码来看。

  1. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {

  2.    List<Match> matches = new ArrayList<Match>();

  3.    //lookupPath就是uri,拿到mapping。以/user/index为例,mapping如下:

  4.    //[{[/user/index],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}]

  5.    List<T> directPathMatches = this.urlMap.get(lookupPath);

  6.    if (directPathMatches != null) {

  7.        //从handlerMethods容器中拿到Method对象,封装成Match对象

  8.        //Match对象其实就两个属性 mapping和handlerMethod对象

  9.        addMatchingMappings(directPathMatches, matches, request);

  10.    }

  11.    HandlerMethod handler = matches.get(0).handlerMethod;


  12.    //最后返回的是HandlerExecutionChain对象

  13.    //里面包含两个属性。handler>就是HandlerMethod对象

  14.    //还有个拦截器列表。interceptorList,在第三步调用拦截器就是循环这个List来调用

  15.    return getHandlerExecutionChain(handler, request);

  16. }

2.2、获取适配器 getHandlerAdapter

这个比较简单。在初始化的时候,注册了几个适配器。判断上一步拿到的Handler是什么类型,就返回什么适配器,这里返回的是RequestMappingHandlerAdapter实例。

2.3、调用拦截器 (方法调用前执行)

拦截器是链式调用,因为可能会有多个拦截器。拦截器的第一个方法,也是预处理方法preHandle是有返回值的。如果返回false,整个请求就到此结束。在业务里,我们可以让这个拦截器做一些校验工作,不符合预期就返回false。需要注意的是interceptorIndex 这个变量,它记录当前调用到了第几个拦截器。为什么要记录这个呢?等看到后置拦截的时候我们就知道了。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) {    if (getInterceptors() != null) {        for (int i = 0; i < getInterceptors().length; i++) {            HandlerInterceptor interceptor = getInterceptors()[i];            //preHandle方法如果返回false,接着就调用拦截器的后置方法。            //因为整个请求已经结束了            if (!interceptor.preHandle(request, response, this.handler)) {                triggerAfterCompletion(request, response, null);                return false;            }            this.interceptorIndex = i;        }    }    return true;}

2.4、方法调用

方法调用就是解析请求的参数,拿到Method对象直接invoke即可。调用之后根据返回值渲染视图。

2.4.1、参数解析

我们在上一章节已经看到,SpringMVC初始化的时候,加载注册了很多解析器的类,其中有参数解析器和返回值类型解析器,如今就派上用场。

一个HTTP请求的方法,不管有多少参数,有多少种参数的类型。最终,它们都是从哪来呢?没错,就是Request。一切从Request而来。参数解析的方法最终返回的是一个Object[] args,就是参数值的数组。

  • 拿到方法上的参数列表,循环此列表

  • 调用解析器来解析参数。它们的顶层接口是 HandlerMethodArgumentResolver。它只有两个方法,supportsParameter、resolveArgument。解析过程全靠这两个方法,supports用来判断是否应该由此类解析,resolve才是真正解析。

  • 返回参数值

private Object[] getMethodArgumentValues(NativeWebRequest request,       ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {    //获取方法参数列表    MethodParameter[] parameters = getMethodParameters();    //args就是解析后的参数值的数组    Object[] args = new Object[parameters.length];    for (int i = 0; i < parameters.length; i++) {        MethodParameter parameter = parameters[i];        if (args[i] != null) {            continue;        }        //判断parameter是否能被某一个解析器所解析        if (this.argumentResolvers.supportsParameter(parameter)) {            try {                //拿到上一步的解析器,解析拿到返回值放入args                args[i] = this.argumentResolvers.resolveArgument(                        parameter, mavContainer, request, this.dataBinderFactory);                continue;            }        }    }    return args;}

下面我们看一下HttpServletRequest在解析器里面具体的实现。这个参数会调用到ServletRequestMethodArgumentResolver解析器,里面其实是一些if else。

  1. public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,

  2.        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {


  3.    Class<?> paramType = parameter.getParameterType();

  4.    if (WebRequest.class.isAssignableFrom(paramType)) {

  5.        return webRequest;

  6.    }

  7.    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);

  8.    if (ServletRequest.class.isAssignableFrom(paramType) ) {

  9.        Object nativeRequest = webRequest.getNativeRequest(paramType);

  10.        return nativeRequest;

  11.    }

  12.    else if (HttpSession.class.isAssignableFrom(paramType)) {

  13.        return request.getSession();

  14.    }

  15.    else if (HttpMethod.class.equals(paramType)) {

  16.        return ((ServletWebRequest) webRequest).getHttpMethod();

  17.    }

  18.    else if (Principal.class.isAssignableFrom(paramType)) {

  19.        return request.getUserPrincipal();

  20.    }

  21.    else if (Locale.class.equals(paramType)) {

  22.        return RequestContextUtils.getLocale(request);

  23.    }

  24.    else if (InputStream.class.isAssignableFrom(paramType)) {

  25.        return request.getInputStream();

  26.    }

  27.    else if (Reader.class.isAssignableFrom(paramType)) {

  28.        return request.getReader();

  29.    }

  30.    //未完......

  31. }

2.4.2、invoke

上一步通过各种解析器之后,返回一个Object类型的参数数组。有了Method对象,参数,调用就变得简单了。

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,        Object... providedArgs) throws Exception {    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);    Object returnValue = doInvoke(args);    if (logger.isTraceEnabled()) {        logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");    }    return returnValue;}
2.4.3、返回值的解析

invoke之后,方法返回Object 类型的returnValue。上面说了,解析器分为参数解析器和返回值解析器两种。So,返回值解析器就是在这里被调用。

它的解析和参数解析器套路基本一致,先判断是否该由此类解析,然后交由该类解析。记得刚工作的时候,只要是返回页面的,都是通过 newModelAndView().setName("xxx")来返回,后来发现直接返回视图名字的字符串也可以,大感惊奇。原来,SpringMVC是在这里处理的。 我们来看这个类 ViewNameMethodReturnValueHandler的解析方法。

public void handleReturnValue(Object returnValue, MethodParameter returnType,        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {    //如果返回的值是字符串类型    //比如 /index,setViewName的工作它来搞    if (returnValue instanceof String) {        String viewName = (String) returnValue;        mavContainer.setViewName(viewName);        if (isRedirectViewName(viewName)) {            mavContainer.setRedirectModelScenario(true);        }    }}

SpringMVC比较重要的一点是支持restful,是通过ResponseBody注解。它又是怎么处理的呢?在注册HandlerAdapter的时候,默认给它添加了消息转换器。里面有7种类型,其中有一个MappingJacksonHttpMessageConverter就是专门负责ResponseBody注解的。

来到 RequestResponseBodyMethodProcessor类,它来负责匹配解析ResponseBody注解。判断方法很简单

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

再来看它的handle方法。

public void handleReturnValue(Object returnValue, MethodParameter returnType,        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)        throws IOException, HttpMediaTypeNotAcceptableException {    //这个属性很重要,在此设置为true,先记住,下面再看。    mavContainer.setRequestHandled(true);    //具体用消息转换器来写入,这里的消息转换器就是    //MappingJacksonHttpMessageConverter    writeWithMessageConverters(returnValue, returnType, webRequest);}
  1. protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,

  2.            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)

  3.            throws IOException, HttpMediaTypeNotAcceptableException {


  4.    //...........省略大部分代码

  5.    //设置数据格式编码等 application/json;charset=UTF-8

  6.    if (selectedMediaType != null) {

  7.        selectedMediaType = selectedMediaType.removeQualityValue();

  8.        //messageConverters就是包含MappingJacksonHttpMessageConverter在内的多种转换器

  9.        for (HttpMessageConverter<?> messageConverter : this.messageConverters) {

  10.            //先判断是否可写 判断方式也很简单,就是看mediaType是否是application/json

  11.            if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {

  12.                //拿到返回值

  13.                returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,

  14.    (Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);

  15.                if (returnValue != null) {

  16.                    //写的过程,先设置Response的头信息、编码,再调用jackson.databind包里的方法写入

  17.                ((HttpMessageConverter<T>) messageConverter).write(returnValue,

  18.                                     selectedMediaType, outputMessage);

  19.                    //写完之后刷新

  20.                    //outputMessage.getBody().flush();

  21.                }

  22.                return;

  23.            }

  24.        }

  25.    }

  26. }

2.4.4、获取ModelAndView

方法调用完了,返回值也都解析了。视图需不需要返回,返回到哪里?ModelAndView来决定。

  1. private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,

  2.            ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {


  3.    //在解析ResponseBody的时候,我们说有个属性很重要。mavContainer.setRequestHandled(true);

  4.    //在这里就用到了,说明不需要视图

  5.    if (mavContainer.isRequestHandled()) {

  6.        return null;

  7.    }

  8.    ModelMap model = mavContainer.getModel();

  9.    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);

  10.    if (!mavContainer.isViewReference()) {

  11.        mav.setView((View) mavContainer.getView());

  12.    }

  13.    return mav;

  14. }

行文至此,关于方法调用算是都已经处理完毕了。我们可以看出来,其实调用很简单, 关键在于做好参数解析和返回值解析的工作。

2.5、调用拦截器 (方法调用后执行)

又到了一个拦截器的调用。这次它调用的是postHandle方法。

for (int i = getInterceptors().length - 1; i >= 0; i--) {    HandlerInterceptor interceptor = getInterceptors()[i];    interceptor.postHandle(request, response, this.handler, mv);}

2.6、处理结果

这里是真正响应请求的地方。先是判断ModelAndView是否为空,如果为空说明返回的不是视图,就没必要往下执行。

if (mv != null && !mv.wasCleared()) {    render(mv, request, response);    if (errorView) {        WebUtils.clearErrorRequestAttributes(request);    }}

重点在于render方法。它最终调用了renderMergedOutputModel方法,渲染输出。我们在配置文件中,都要配置一个视图解析器viewResolver,这里就是用到的它。解析出来完整的视图路径后,利用Servlet的RequestDispatcher直接做转发就完成了这一步的工作。

protected void renderMergedOutputModel(            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {    //获取返回的路径 视图解析器配置的prefix加上返回的viewName,再加上后缀p:suffix    String dispatcherPath = prepareForRendering(requestToExpose, response);    //获取RequestDispatcher,    RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);    //直接转发    rd.forward(requestToExpose, response);}

2.7、调用拦截器 (视图渲染后执行)

在调用拦截器的预处理方法时,提到了一个变量:interceptorIndex 。在这里就能看到它的作用。拦截器可能是多个的,它是链式调用的过程。比如有5个拦截器。如果在第3个拦截器的preHandler方法返回了false,后两个拦截器的After不应该再被执行。所以在后置拦截方法是从interceptorIndex 开始的。

void triggerAfterCompletion(HttpServletRequest request,         HttpServletResponse response, Exception ex)throws Exception {    for (int i = this.interceptorIndex; i >= 0; i--) {        HandlerInterceptor interceptor = getInterceptors()[i];        try {            interceptor.afterCompletion(request, response, this.handler, ex);        }    }}
3、总结

以上就是SpringMVC处理一个请求的所有流程。DispatcherServlet其本质就是个Servlet,一切请求经过它来处理。它根据请求的uri找到对应的Method对象,然后从Request中拿到参数解析成我们想要的类型,调用具体方法。通过不同的返回值解析器来确定返回的数据的类型是什么,需不需要响应视图。然后调用RequestDispatcher 直接转发。最后通过HandlerInterceptor可以让我们有机会参与到SpringMVC处理环节中去,在具体方法执行的不同时机加入我们自定义的业务。