• DispatcherServlet的逻辑处理(上篇)


    在HttpServlet类中分别提供了相应的服务方法,它们是doDelete、doGet、doOptions、doPost、doPut和doTrace,它会根据不同的请求形式将程序引导至对应的函数处理,这几个函数中最常用的就是doGet和doPost,那么我们就直接查看DispatcherServlet中对于这两个函数的逻辑实现。

    @Override
        protected final void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
    
            processRequest(request, response);
        }
    @Override
        protected final void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
    
            processRequest(request, response);
        }

    对于不同的方法,Spring并没有做特殊的处理,而是统一将程序再一次的引导至processRequest(request, response)。

    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            //记录当前时间,用于计算web请求的处理时间
            long startTime = System.currentTimeMillis();
            Throwable failureCause = null;
    
            LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
            LocaleContext localeContext = buildLocaleContext(request);
    
            RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    
            initContextHolders(request, localeContext, requestAttributes);
    
            try {
                doService(request, response);
            }
            catch (ServletException | IOException ex) {
                failureCause = ex;
                throw ex;
            }
            catch (Throwable ex) {
                failureCause = ex;
                throw new NestedServletException("Request processing failed", ex);
            }
    
            finally {
                resetContextHolders(request, previousLocaleContext, previousAttributes);
                if (requestAttributes != null) {
                    requestAttributes.requestCompleted();
                }
                logResult(request, response, failureCause, asyncManager);
                publishRequestHandledEvent(request, response, startTime, failureCause);
            }
        }

    上述函数已经开始了对请求的处理,虽然把细节转移到了doService函数中实现,但是我们不难看出请求前后所做的准备与处理工作。

    (1)为了保证当前线程的LocaleContext以及RequestAttributes可以在当前请求后还能恢复,提取了当前线程的两个属性。

    (2)根据当前request创建对应的LocaleContext和RequestAttributes,并绑定到当前线程。

    (3)委托给doService方法进行进一步处理。

    (4)请求处理结束后恢复线程到原始状态。

    (5)请求处理结束后无论成功与否发布事件通知。

    继续查看doService方法:

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
            logRequest(request);
    
            // Keep a snapshot of the request attributes in case of an include,
            // to be able to restore the original attributes after the include.
            Map<String, Object> attributesSnapshot = null;
            if (WebUtils.isIncludeRequest(request)) {
                attributesSnapshot = new HashMap<>();
                Enumeration<?> attrNames = request.getAttributeNames();
                while (attrNames.hasMoreElements()) {
                    String attrName = (String) attrNames.nextElement();
                    if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                        attributesSnapshot.put(attrName, request.getAttribute(attrName));
                    }
                }
            }
    
            // 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());
    
            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 {
                doDispatch(request, response);
            }
            finally {
                if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                    // Restore the original attribute snapshot, in case of an include.
                    if (attributesSnapshot != null) {
                        restoreAttributesAfterInclude(request, attributesSnapshot);
                    }
                }
            }
        }

    我们猜想对请求处理至少应该包括一些诸如寻找Handler并页面跳转之类的逻辑处理,但是,在doService中我们并没有看到相应的逻辑,相反同样是一些准备工作,但是这些准备工作却是必不可少的。Spring将已经初始化的功能辅助工具变量,比如localeResolver、themeResolver等设置在Request属性中,而这些属性会在接下来的处理中派上用场。

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            HttpServletRequest processedRequest = request;
            HandlerExecutionChain mappedHandler = null;
            boolean multipartRequestParsed = false;
    
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;
    
                try {
                    //如果是MultipartContext类型的request则转换request为MultipartHttpServletRequest类型的request
                    processedRequest = checkMultipart(request);
                    multipartRequestParsed = (processedRequest != request);
    
                    //根据request信息寻找对应的handler
                    mappedHandler = getHandler(processedRequest);
                    if (mappedHandler == null) {
                        noHandlerFound(processedRequest, response);
                        return;
                    }
    
                    // 根据当前的handler寻找对应的HandlerAdapter
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
                    // 如果是当前的handler支持last-modified头处理
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
    
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
    
                    //实际调用处理程序。
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
    
                    applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                }
                catch (Exception ex) {
                    dispatchException = ex;
                }
                catch (Throwable err) {
                    // As of 4.3, we're processing Errors thrown from handler methods as well,
                    // making them available for @ExceptionHandler methods and other scenarios.
                    dispatchException = new NestedServletException("Handler dispatch failed", err);
                }
                processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            }
            catch (Exception ex) {
                triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
            }
            catch (Throwable err) {
                triggerAfterCompletion(processedRequest, response, mappedHandler,
                        new NestedServletException("Handler processing failed", err));
            }
            finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    // Instead of postHandle and afterCompletion
                    if (mappedHandler != null) {
                        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                    }
                }
                else {
                    // Clean up any resources used by a multipart request.
                    if (multipartRequestParsed) {
                        cleanupMultipart(processedRequest);
                    }
                }
            }
        }
    private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
            if (mv != null && !mv.hasView()) {
                String defaultViewName = getDefaultViewName(request);
                if (defaultViewName != null) {
                    mv.setViewName(defaultViewName);
                }
            }
        }
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
                throws Exception {
    
            HandlerInterceptor[] interceptors = getInterceptors();
            if (!ObjectUtils.isEmpty(interceptors)) {
                for (int i = interceptors.length - 1; i >= 0; i--) {
                    HandlerInterceptor interceptor = interceptors[i];
                    interceptor.postHandle(request, response, this.handler, mv);
                }
            }
        }

    doDispatch函数中展示了Spring请求处理所涉及的主要逻辑,而我们之前设置在request中的各种辅助属性也都有被派上了用场。

    下面的文章将回顾逻辑处理的全过程。

    参考:《Spring源码深度解析》 郝佳 编著:

  • 相关阅读:
    js实现input button从不可用变为可用
    eclipse工程名出现小红叉的解决办法
    jQuery的dataTables插件实现中文排序
    禁用鼠标选中DOM
    获取当前JS所在文件夹
    解决方案:在移动端输入框在软键盘弹出后位置矫正
    MUI版本升级更新程序IOS和andriod
    JavaScript输入表单数据正则验证规则
    Windows安裝PHP環境
    CentOS下安装PHP
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/10303885.html
Copyright © 2020-2023  润新知