Spring源码分析(五)SpringMVC是怎样处理请求的?

前言

上一节我们看到了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()。

protected void doService(HttpServletRequest request, HttpServletResponse response) {
    // Make framework objects available to handlers and view objects.
    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());

    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(request, response);
}

2、doDispatch

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

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        ModelAndView mv = null;
        Exception dispatchException = null;
        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);
            //第一步 确定请求的处理器
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                //URL没有找到匹配项,返回404
                noHandlerFound(processedRequest, response);
                return;
            }
            // 第二步 确定请求的处理适配器
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            //第三步 调用拦截器 (方法调用前执行)
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }
            // 第四步  方法调用 
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            //第五步 调用拦截器  (方法调用后执行)
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        //第六步 处理方法返回 (返回页面或者属性)
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        //第七步 调用拦截器(请求完成后执行)
        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    }
}

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

2.1、 获取处理器 getHandler

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

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    List matches = new ArrayList();
    //lookupPath就是uri,拿到mapping。以/user/index为例,mapping如下:
    //[{[/user/index],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}]
    List directPathMatches = this.urlMap.get(lookupPath);
    if (directPathMatches != null) {
        //从handlerMethods容器中拿到Method对象,封装成Match对象
        //Match对象其实就两个属性 mapping和handlerMethod对象
        addMatchingMappings(directPathMatches, matches, request);
    }
    HandlerMethod handler = matches.get(0).handlerMethod;

    //最后返回的是HandlerExecutionChain对象
    //里面包含两个属性。handler>就是HandlerMethod对象
    //还有个拦截器列表。interceptorList,在第三步调用拦截器就是循环这个List来调用
    return getHandlerExecutionChain(handler, request);
}

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。

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

    Class paramType = parameter.getParameterType();
    if (WebRequest.class.isAssignableFrom(paramType)) {
        return webRequest;
    }
    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
    if (ServletRequest.class.isAssignableFrom(paramType) ) {
        Object nativeRequest = webRequest.getNativeRequest(paramType);
        return nativeRequest;
    }
    else if (HttpSession.class.isAssignableFrom(paramType)) {
        return request.getSession();
    }
    else if (HttpMethod.class.equals(paramType)) {
        return ((ServletWebRequest) webRequest).getHttpMethod();
    }
    else if (Principal.class.isAssignableFrom(paramType)) {
        return request.getUserPrincipal();
    }
    else if (Locale.class.equals(paramType)) {
        return RequestContextUtils.getLocale(request);
    }
    else if (InputStream.class.isAssignableFrom(paramType)) {
        return request.getInputStream();
    }
    else if (Reader.class.isAssignableFrom(paramType)) {
        return request.getReader();
    }
    //未完......
}
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,返回值解析器就是在这里被调用。

它的解析和参数解析器套路基本一致,先判断是否该由此类解析,然后交由该类解析。记得刚工作的时候,只要是返回页面的,都是通过new ModelAndView().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);
}

protected  void writeWithMessageConverters(T returnValue, MethodParameter returnType,
            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
            throws IOException, HttpMediaTypeNotAcceptableException {

    //...........省略大部分代码 
    //设置数据格式编码等 application/json;charset=UTF-8
    if (selectedMediaType != null) {
        selectedMediaType = selectedMediaType.removeQualityValue();
        //messageConverters就是包含MappingJacksonHttpMessageConverter在内的多种转换器
        for (HttpMessageConverter messageConverter : this.messageConverters) {
            //先判断是否可写 判断方式也很简单,就是看mediaType是否是application/json
            if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
                //拿到返回值
                returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
                        (Class>) messageConverter.getClass(), inputMessage, outputMessage);
                if (returnValue != null) {
                    //写的过程,先设置Response的头信息、编码,再调用jackson.databind包里的方法写入
                    ((HttpMessageConverter) messageConverter).write(returnValue, selectedMediaType, outputMessage);
                    //写完之后刷新
                    //outputMessage.getBody().flush();
                }
                return;
            }
        }
    }
}
2.4.4、获取ModelAndView

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

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
            ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    
    //在解析ResponseBody的时候,我们说有个属性很重要。mavContainer.setRequestHandled(true);
    //在这里就用到了,说明不需要视图
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    ModelMap model = mavContainer.getModel();
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    return mav;
}

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

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 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处理环节中去,在具体方法执行的不同时机加入我们自定义的业务。

你可能感兴趣的:(Spring源码分析(五)SpringMVC是怎样处理请求的?)