美文网首页
Spring MVC 源码分析之 DispatcherServl

Spring MVC 源码分析之 DispatcherServl

作者: 瞎胡扯1 | 来源:发表于2020-11-30 07:06 被阅读0次

    一、概述

    MVC大家比较熟悉

    • M即model,是业务处理层,与我们开发中的(service、dao、model)等对应起来;
    • V即view,是视图层,以前jsp、freemarker、velocity等,现在都是前后端分离了。使用@ResponseBody注解把Controller方法返回的对象通过转换器转换成指定的格式(如json/xml/protobuf)后,再写入到Response对象的body区,不再走视图解析器,把渲染到工作交给前端去做。
    • C,即controller,控制器,可以分为前端控制器(负责请求的分发,即DispatcherServlet)、映射处理器(uri与处理方法的映射,即HandlerMapping)、业务控制器(即我们的controller层)、视图解析器(即ViewResolver)。

    二、类图

    image image.gif

    如上图类的继承关系可知,DispatcherServlet就是一个Servlet.

    三、初始化过程

    了解Servlet的都知道在Servlet中主要的方法有:

    • init 初始化方法
    • service 用于处理请求的方法
    • destroy servlet的销毁方法

    3.1、HttpServletBean类初始化方法 init

    public final void init() throws ServletException {
    
            // Set bean properties from init parameters.
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            if (!pvs.isEmpty()) {
                try {
                    //将Servlet中配置的参数封装到pvs变量中,requiredProperties为必须参数,如果没
                    //配置将报异常
                    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                    ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                    bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
    
                     //模板方法,可以在子类中实现,做一些初始化工作,
                    initBeanWrapper(bw);
    
                    bw.setPropertyValues(pvs, true);
                }
                catch (BeansException ex) {
                    if (logger.isErrorEnabled()) {
                        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                    }
                    throw ex;
                }
            }
    
            // Let subclasses do whatever initialization they like.
            // 模板方法,子类初始化入口方法,在子类中可以做一些具体的实现方法
            initServletBean();
        }
    
    image.gif

    3.2、在 FrameworkServlet 中实现了 initServletBean 方法

    @Override
        protected final void initServletBean() throws ServletException {
            // 略 .....
            try {
    
                //主要方法,主要实现了 初始化 WebApplicationContext 
                this.webApplicationContext = initWebApplicationContext();
                initFrameworkServlet();
            }
            catch (ServletException | RuntimeException ex) {
                logger.error("Context initialization failed", ex);
                throw ex;
            }
             // 略 .....
            }
        }
    
    image.gif

    3.3、initWebApplicationContext 方法

    protected WebApplicationContext initWebApplicationContext() {
    
          //  略...
    
            if (!this.refreshEventReceived) {
                // Either the context is not a ConfigurableApplicationContext with refresh
                // support or the context injected at construction time had already been
                // refreshed -> trigger initial onRefresh manually here.
                synchronized (this.onRefreshMonitor) {
                    onRefresh(wac);  //重点方法
                }
            }
    
            if (this.publishContext) {
                // Publish the context as a servlet context attribute.
                String attrName = getServletContextAttributeName();
                getServletContext().setAttribute(attrName, wac);
            }
    
            return wac;
        }
    
    image.gif

    initWebApplicationContext方法做了三件事:

    1. 获取spring的根容器rootContext。
    2. 设置webApplicationContext并根据情况调用onRefresh方法。
    3. 将webApplicationContext设置到ServletContext中。

    3.4、DispatcherServlet中的 onRefresh 方法

    @Override
        protected void onRefresh(ApplicationContext context) {
            initStrategies(context);
        }
    
        /**
         * Initialize the strategy objects that this servlet uses.
         * <p>May be overridden in subclasses in order to initialize further strategy objects.
         */
        protected void initStrategies(ApplicationContext context) {
    
            //初始化多媒体解析器,这个不分析
            initMultipartResolver(context);
          //初始化国际化解析器,这个不进行分析
            initLocaleResolver(context);
          //初始化主题解析器,基本不用,不进行分析
            initThemeResolver(context);
          //初始化映射器
            initHandlerMappings(context);
          //初始化适配器
            initHandlerAdapters(context);
          //初始化异常解析器
            initHandlerExceptionResolvers(context);
          //初始化视图名转换器,不进行分析
            initRequestToViewNameTranslator(context);
          //初始化视图解析器,前端分离后,用得少了,也分析下
            initViewResolvers(context);
          //初始化FlashMapManager,只知道是重定向时用来保存数据用的,没有使用过,这里不进行分析
            initFlashMapManager(context);
        }
    
    image.gif

    下面贴的代码的几个初始化代码的逻辑其实是一样的,分为三步,以initHandlerMappings进行说明

    1. 判断是否寻找所有容器中实现HandlerMapping接口的,是则寻找所有容器中实现了HandlerMapping接口的对象,设置到handlerMappings对象中并排序(平常开发中走的都是这个分支)
    2. 否的话,只找寻到前容器实现了HandlerMapping接口的对象
    3. 如果上面查找后为空的话,则加载配置文件中的类并实例化,再设置到handlerMappings中。注意的是:这个配置文件位置是:org/springframework/web/servlet/DispatcherServlet.properties

    至此,DispatcherServlet的初始化工作已经完成。

    四、请求过程

    上文介绍过 Servlet中处理请求的为 service 方法。下面先看service方法的处理逻辑

    4.1、HttpServlet中的 service方法

     protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
    
            String method = req.getMethod();
    
            if (method.equals(METHOD_GET)) {
                long lastModified = getLastModified(req);
                if (lastModified == -1) {
                    // servlet doesn't support if-modified-since, no reason
                    // to go through further expensive logic
                    doGet(req, resp);
                } else {
                    long ifModifiedSince;
                    try {
                        ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                    } catch (IllegalArgumentException iae) {
                        // Invalid date header - proceed as if none was set
                        ifModifiedSince = -1;
                    }
                    if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                        // If the servlet mod time is later, call doGet()
                        // Round down to the nearest second for a proper compare
                        // A ifModifiedSince of -1 will always be less
                        maybeSetLastModified(resp, lastModified);
                        doGet(req, resp);
                    } else {
                        resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                    }
                }
    
            } else if (method.equals(METHOD_HEAD)) {
                long lastModified = getLastModified(req);
                maybeSetLastModified(resp, lastModified);
                doHead(req, resp);
    
            } else if (method.equals(METHOD_POST)) {
                doPost(req, resp);
    
            } else if (method.equals(METHOD_PUT)) {
                doPut(req, resp);
    
            } else if (method.equals(METHOD_DELETE)) {
                doDelete(req, resp);
    
            } else if (method.equals(METHOD_OPTIONS)) {
                doOptions(req,resp);
    
            } else if (method.equals(METHOD_TRACE)) {
                doTrace(req,resp);
    
            } else {
                //
                // Note that this means NO servlet supports whatever
                // method was requested, anywhere on this server.
                //
    
                String errMsg = lStrings.getString("http.method_not_implemented");
                Object[] errArgs = new Object[1];
                errArgs[0] = method;
                errMsg = MessageFormat.format(errMsg, errArgs);
    
                resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
            }
        }
    
    image.gif

    从源码中可得知 HttpServlet中 service 方法根据请求方法,把请求具体交给了 doGet、doPost、doPut等方法来处理

    4.2、FrameworkServlet 中的 service 方法

    在FrameworkServlet中重写了 service 方法同时也重写了 doGet、doPost,doPut等方法,源码如下所示

    @Override
        protected void service(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
    
            HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
            if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
                processRequest(request, response);
            }
            else {
                super.service(request, response);
            }
        }
    
        @Override
        protected final void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
    
            // 主要通过此方法实现请求的分发
            processRequest(request, response);
        }
    
    image.gif

    有源码可知,在doGet 方法中有调用了 processRequest方法,其他doXX中同样调用了此方法,接下来看看 此方法。

    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
    
            long startTime = System.currentTimeMillis();
            Throwable failureCause = null;
             // 获取LocaleContextHolder 中保存的LocalContext
            LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    
            //获取当前请求的LocaleContext
            LocaleContext localeContext = buildLocaleContext(request);
    
            //获取RequestContextHolder中保存的RequestAttributes
            RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
            //获取当前请求的ServletRequestAttributes
            ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    
            // 略 .....
    
            //将当前请求的LocaleContext和ServletRequestAttributes保存到
            //LocaleConextHolder和RequestContextHolder中
            initContextHolders(request, localeContext, requestAttributes);
    
            try {
                //模板方法,在子类中实现
                doService(request, response);
            }catch (ServletException | IOException ex) {
                //略 ....
            }catch (Throwable ex) {
                //略 ....
            }finally {
                 //恢复之前的LocaleContext和ServletRequestAttributes到
                 //LocaleConextHolder和RequestContextHolder中
                resetContextHolders(request, previousLocaleContext, previousAttributes);
                if (requestAttributes != null) {
                    requestAttributes.requestCompleted();
                }
    
                //发布ServletRequestHandledEvent消息
                publishRequestHandledEvent(request, response, startTime, failureCause);
            }
        }
    
    image.gif

    说明: 由于在请求时把LocaleContext和ServletRequestAttributes保存到了LocaleContextHolder和RequestContextHolder中,这两个类都是在ThreadLocal中所有在后续的方法中可以根据通过这两个类获取LocalContext和ServletRequestAttributes,从而获取HttpServletRequest、HttpServletResponse和HttpSession。

    4.3、DispatcherServlet中实现了 doService方法,

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
             //省略 部分代码 .....
    
            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);
                    }
                }
            }
        }
    
    image.gif

    有源码可知 doService方法中主要是调用了 doDispatch方法,接下来看看此方法的实现

    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 {
                    //检测是否为上传请求
                    processedRequest = checkMultipart(request);
                    multipartRequestParsed = (processedRequest != request);
    
                    // Determine handler for the current request.
                    //根据 request 查找对应的 Handler 
                    mappedHandler = getHandler(processedRequest);
                    if (mappedHandler == null) {
                        noHandlerFound(processedRequest, response);
                        return;
                    }
    
                    // Determine handler adapter for the current request.
                    //根据 Handler查找执行此Handler的 handler适配器
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
                    // Process last-modified header, if supported by the handler.
                    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;
                    }
    
                    // Actually invoke the handler.
                    //实际调用 Handler
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
    
                    applyDefaultViewName(processedRequest, mv);
                    //执行拦截器中的 postHandle 方法
                    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);
                }
                //处理返回结果。包括处理异常、渲染页面、发出完成通知调用拦截器的 afterComletion方法
                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);
                    }
                }
            }
        }
    
    image.gif

    五、总结

    本篇文章主要是分析了DispatcherServlet 的初始化和请求的响应过程,接下来将会分析 Handler的查找过程。

    相关文章

      网友评论

          本文标题:Spring MVC 源码分析之 DispatcherServl

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