SpringMVC源码分析--分析SpringMVC请求的处理流程(二)

SpringMVC请求处理核心方法主要是如下调用:

 (FramworkServlet 的方法)  processRequest <----

(DispatcherServlet的方法)  doService <----  doDispatch  <----  processDispatchResult  <-------  render方法

1.HttpServletBean

      这个类主要参与了创建配置环境,没有涉及到请求的处理。

2.FrameworkServlet

     由于FrameworkServlet也是间接继承了HttpServlet,在这个类中重写了service,doGet,doPost,doPut,doDelete,doOptions,doTrace方法,并且在service中还增加了对于PATCH类型请求的处理。并且重写的所有请求又统一交给了processRequest方法统一进行处理。举例,其他类似:

   protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
            super.service(request, response);  //这里任然代用HttpServelt中的service方法进行分发处理,这里也是为了防止做一些特殊请求,不能完全的调用覆盖的service方法。
        } else {
            this.processRequest(request, response);
        }

    }

    protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }

   2.1 统一处理请求的方法processRequest

             processRequest方法主要做的事:

                    1) 异步请求

                    2)调用了doService模板方法

                    3)对LocaleContext(这是针对国际化的环境设置)和RequestAttributes(涉及到request和session的设置)的设置和恢复

                    4)处理完消息后发布了ServeltRequestHandledEvent消息

  protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    //获取LocaleContextHolder中原来保存的LocaleContext
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    //获取当前请求的LocaleContext
    LocaleContext localeContext = buildLocaleContext(request);
    //获取RequestContextHolder中原来保存的RequestAttributes
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    //获取当前请求的ServletRequestAttributes
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    // 异步操作管理
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    //将当前请求的LocaleContext和ServletRequestAttributes设置到LocaleContextHolder和RequestContextHolder中
    initContextHolders(request, localeContext, requestAttributes);

    try {
        //具体处理方法入口,这是一个模板方法(抽象的,由子类实现)
        doService(request, response);
    }
    catch (ServletException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }
    finally {
        //恢复原来的LocaleContext和ServiceRequestAttributes到LocaleContextHolder和RequestContextHolder,避免影响Servlet以外的处理,如Filter
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();  //这里执行了request后有一个requestActive状态值会转变为false,执行前是true
        }

        if (logger.isDebugEnabled()) {
            if (failureCause != null) {
                this.logger.debug("Could not complete request", failureCause);
            }
            else {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    logger.debug("Leaving response open for concurrent processing");
                }
                else {
                    this.logger.debug("Successfully completed request");
                }
            }
        }
        //发布ServletRequestHandlerEvent消息,这个请求是否执行成功都会发布消息的
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

     方法和相关类的解释:

     1) LocalContextHolders 这是一个abstract类,但里面的方法都是static的,可以直接调用,而且没有父类也没子类,因而我们不能对其进行实例化,只能调用其static方法。这种abstract的使用方式值得我们学习。在LocaleContextHolder中定义了两个属性:          

private static final ThreadLocal<LocaleContext> localeContextHolder =
            new NamedThreadLocal<LocaleContext>("Locale context");

private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
            new NamedInheritableThreadLocal<LocaleContext>("Locale context");

    LocaleContextHolder还提供了get/set方法,可以获取和设置LocaleContext,另外还提供了get/setLocale方法,可以直接操作Locale,保存在ThreadLocal中能够保证多个请求之间相互独立,互不影响,对于ThreadLocal的作用及其实现原理,我们将在后面的文章中进行说明。

     LocaleContextHolder抽象类目的:为了方便在项目的任何地方使用Locale,而不需要将其作为参数进行传递到对应的地方,只需要调用一下LocaleContextHolder的getLocale()方法即可。例如如果我们再service层调用local的时候就可以直接通过LocalContextHolders.getLocal()调用而不需要依赖request。

      2)RequestAttributesHolder也是一样的道理,里面封装了RequestAttributes,可以get/set/removeAttribute,而且因为实际封装的是ServletRequestAttributes,因此还可以通过get/set方法获取或修改request、response、session对象。

ServletRequestAttributes类中通过scope判断是request还是session源码:

    public void setAttribute(String name, Object value, int scope) {
        if (scope == 0) {
            if (!this.isRequestActive()) {
                throw new IllegalStateException("Cannot set request attribute - request is not active anymore!");
            }

            this.request.setAttribute(name, value);
        } else {
            HttpSession session = this.obtainSession();
            this.sessionAttributesToUpdate.remove(name);
            session.setAttribute(name, value);
        }

    }

   3)publishRequestHandledEvent()发布请求处理完后的事件源码

private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, long startTime, @Nullable Throwable failureCause) {
        //当publishEvents设置为true和 webApplicationContext 不为空就会处理这个事件的发布
        if (this.publishEvents && this.webApplicationContext != null) {
            long processingTime = System.currentTimeMillis() - startTime;
            this.webApplicationContext.publishEvent(new ServletRequestHandledEvent(this, 
                request.getRequestURI(), request.getRemoteAddr(), request.getMethod(), 
                this.getServletConfig().getServletName(), WebUtils.getSessionId(request), 
                this.getUsernameForRequest(request), processingTime, failureCause,   
                  response.getStatus()));
        }

    }

   那么怎么使用这个监听事件了,只需要的实现ApplicationListener这个接口就可以,可以用来做记录日志的监听器,并且只需要把自己需要做的事情写到onApplicationEvent里面就可以了,当然需要把这个注册到容器中,这里用注解Componet实现。

代码例子:

@Componet
public class ServletReqestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent> {
    final static Logger logger = LoggerFactory.getLogger("RequestProcessing");
    @Override
    public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledEvent) {
        logger.info(servletRequestHandledEvent.getDescription());
    }
}

3.DispatcherServlet

         1) doService方法

      DispatcherServlet里面执行处理的入口方法是doService,由于这个类继承于FrameworkServlet类,重写模板方法doService().源代码如下:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (this.logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            this.logger.debug("DispatcherServlet with name '" + this.getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }
        //当include请求时对request的Attribute做快照备份
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap();
            Enumeration attrNames = request.getAttributeNames();

            label112:
            while(true) {
                String attrName;
                do {
                    if (!attrNames.hasMoreElements()) {
                        break label112;
                    }

                    attrName = (String)attrNames.nextElement();
                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
        //对request设置一些属性,便于具体处理时调用
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
        //flashMap主要用于Redirect转发时参数的传递
        if (this.flashMapManager != null) {
            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);
        }

        try {
            //request设置完相关的属性做真正的请求处理
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
             //还原request快照的备份
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }

        }

    }

 FlashMap介绍

           上面后面的三个request参数设置都是和flashMap相关,主要用于Redirect转发参数的传递。比如为了避免重复提交表单,可以在处理完post请求后redirect到一个get请求,这样即使用户刷新也不会有重复提交的问题。不过问题是:前面的post请求是提交订单,提交完后redirect到一个显示订单的页面,显然在显示订单的页面需要订单的一些信息,但是redirect本身是没有传递参数的功能的,按照普通模式传递只能将其写入url中,但是url有长度限制,而且有些参数还不能暴露在url中,所以我们就需要 使用flashMap来进行数据传递了,我们需要在redirect之前强参数写入到OUTPUT_FLASH_MAP_ATTRIBUTE如下所示:

((FlashMap)((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().
        getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name","vison");

          这样在redirect之后的handle中spring就会自动将其设置到model中(先设置到INPUT_FLASH_MAP_ATTRIBUTE属性里,然后再放到model中)这样操作显得麻烦,RedirectAttributes有两种设置参数的方法addAttribute(key,value);addFlashAttribute(key,value)第一个方法设置的参数会拼接到url中,第二个方法设置的参数即使保存到flashMap中的。当然下面的outputFlashMap还可以通过RequestContextUtils.getOutFlashMap(request)来获取。

例子:

 @RequestMapping(value = "/submit",method = RequestMethod.POST)
    public String submit(RedirectAttributes attributes){
        //方式一存值
        ((FlashMap)((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().
                getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name","vison");
        //方式二存值
        attributes.addFlashAttribute("ordersId","XXX");
        //方式三存值
        attributes.addAttribute("Local","zh-cn");
        return "redirect:showorders";
    }

    @RequestMapping(value = "/showorders",method = RequestMethod.POST)
    public String showOrders(Model model){
        //  dosometing.....
        return "orders";
    }

如上所示前两种的方式都是存在flashMap中,对用户来说都是透明的,但是第三种这是拼接到url后面的,比如访问http://xxx/submit请求后浏览器地址会自动跳转到http://xxx/showorders?Local=zh-cn上连接。inputFlashMap用来保存上次请求中转发过来的属性,outputFlashMap用于保存本次请求需要转发的属性。下面看doDispatch方法。

2)doDispatch方法

      这个方法是在doService方法中调用的,从底层设计了整个请求的处理流程:

         i:根据request找到Handler;

        ii:根据Handler找到对应的HandlerAdapter;

        iii:用HandlerAdapter处理Handler;

        IV:调用 processDispatchResult方法处理上面之后的结果(包含View渲染并输出给用户)

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    //检查是不是上传请求,是上传请求解析,否则还是返回request
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    //通过请求获取到 HandlerExecutionChain (包含请求和拦截器)
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    //根据Handler找到HandlerAdapter
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    // 处理GET、HEAD请求的Last-Modified
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }
                        //当数据没有更改时,就直接返回上次的数据,提高效率
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
                    //执行相应的Interceptor的preHandle
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    // HandlerAdapter使用Handler处理请求
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                     //如果需要异步处理,直接返回
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    //当view为空时,根据request设置默认view,如Handler返回值为void
                    this.applyDefaultViewName(processedRequest, mv);
                    //执行相应Interceptor的postHandle
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                //处理返回结果,包括处理异常、渲染页面,发出完成通知触发Interceptor的afterCompletion
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                 // 删除上传资源
                this.cleanupMultipart(processedRequest);
            }

        }
    }

解析流程:

    doDispatcher首先检查是不是上传请求,如果是则将request转换为MultipartHttpServletRequest,并将multipartRequestParsed标志设置为true。

    然后通过getHandler获取Handler处理器链HandlerExecutionChain,具体的获取过程,后面分析HandlerMapping时再进行详细分析。

    接下来处理GET、HEAD请求的Last-Modified,当浏览器第一次跟服务器请求资源时,服务器在返回的请求头里会包含一个last-Modified的属性,代表本资源最后是什么时候修改的。在浏览器以后发送请求时会同时发送之前接收到的last-modified,服务器接收到带Last-modified的请求后会用其值和自己实际资源的最后修改时间作对比,如果资源过期了则返回新的资源(同时返回新的last-modified),否则直接返回304状态吗表示资源未过期,浏览器直接使用之前缓存的结果。

     接下来依次调用相应的Interceptor的preHandle。处理完preHandle后就到了此方法最关键的地方——让HandlerAdapter使用Handler处理请求,Controller就是在这执行的,具体内容在分析HandlerAdapter时在详细解释。

     Handler处理完请求后,如果需要异步处理则直接返回,如果不需要异步处理,当view为空时,设置默认view,然后执行相应的Interceptor的postHandle。

   至此,请求处理的内容就完成了,接下来使用processDispatchResult方法处理前面返回的结果,其中包括处理异常、渲染页面、触发Interceptor的afterCompletion方法三部分内容。

知识点集锦:

       1》 Handler:处理器,他直接对应着MVC中的C,也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法,因为它的定义是Object,我们在方法中标注的@RequestMapping的所有方法都可以看成一个Handler,只要可以实际处理请求的都可以看成Handler

       2》HandlerMapping:用来查找Handler的,在SpringMVC中会处理很多请求,每一个请求都需要一个Handler来处理,具体接受到请求后需要哪一个Handler来处理,就需要HandlerMapping来做了。

       3》HandlerAdapter:适配器,不同的Handler需要找到不同HandlerAdapter来调用Handler。就如工厂里需要使用工具干活的人(HandlerAdapter)使用工具(Handler)干活,而HandlerMapping用于根据需要干的活找到相应的工具。

3. processDispatchResult方法

       processDispatchResult方法主要用来处理前面返回的结果,其中包括处理异常、渲染页面、触发Interceptor的afterCompletion方法三部分内容。处理的异常是在处理请求doDispatch方的过程中产生的。

  这里的View和ViewResovler与Handler和HandlerMapping相似,View就是模板用来展示数据的;ViewResovler就是用来查找数据的,总体来看就是HandlerMapping找到干活的Handler,找到使用的HandlerAdapter,让HandlerAdapter使用Handler干活,干完活后将这个结果写个报给交上去(通过View展示给用户)。

       源码:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
        boolean errorView = false;
        //如果请求过程中有异常抛出则处理异常
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                this.logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException)exception).getModelAndView();
            } else {
                Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
                mv = this.processHandlerException(request, response, handler, exception);
                errorView = mv != null;
            }
        }
        //渲染页面
        if (mv != null && !mv.wasCleared()) {
            this.render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");
        }
            
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            //请求处理完,触发Interceptor的afterCompletion
            if (mappedHandler != null) {
                mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
            }

        }
    }

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();   //这里使用localResoler 
        response.setLocale(locale);
        String viewName = mv.getViewName();
        View view;
        if (viewName != null) {
            view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);   //这里使用了ViewResoler获取到实际的View
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
            }
        } else {
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
            }
        }

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'");
        }

        try {
            if (mv.getStatus() != null) {
                response.setStatus(mv.getStatus().value());
            }

            view.render(mv.getModelInternal(), request, response);
        } catch (Exception var8) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'", var8);
            }

            throw var8;
        }
    }

总结:

上面所有的分析可以通过下面的一张图来做总结,中间是doDispatcher的处理流程图,左边是Interceptor相关处理方法的调用位置,右边是doDispatcher方法处理过程中所涉及的组件。图中上半部分的处理请求对应着MVC的Controller即C层,下半部分的processRequestResult主要对应MVC中的View即V层,M层也就是Model贯穿于与整个过程中。

 

猜你喜欢

转载自blog.csdn.net/weixin_40792878/article/details/81638574