美文网首页
Spring源码分析(五)SpringMVC是怎样处理请求的?

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

作者: 清幽之地 | 来源:发表于2018-09-20 14:35 被阅读0次

    前言

    上一节我们看到了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<Match> matches = new ArrayList<Match>();
        //lookupPath就是uri,拿到mapping。以/user/index为例,mapping如下:
        //[{[/user/index],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}]
        List<T> 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 <T> 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<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
                    if (returnValue != null) {
                        //写的过程,先设置Response的头信息、编码,再调用jackson.databind包里的方法写入
                        ((HttpMessageConverter<T>) 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<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处理环节中去,在具体方法执行的不同时机加入我们自定义的业务。

    相关文章

      网友评论

          本文标题:Spring源码分析(五)SpringMVC是怎样处理请求的?

          本文链接:https://www.haomeiwen.com/subject/ukadnftx.html