DispatcherServlet 源码分析

一.DispatcherServlet 源码分析

本文仅了解源码内容即可。
1.观察我们的服务启动⽇志:

当Tomcat启动之后, 有⼀个核⼼的类DispatcherServlet, 它来控制程序的执⾏顺序.所有请求都会先进到DispatcherServlet,执⾏doDispatch 调度⽅法. 如果有拦截器, 会先执⾏拦截器 preHandle() ⽅法的代码, 如果 preHandle() 返回true, 继续访问controller中的⽅法. controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 和 afterCompletion() ,返回给DispatcherServlet, 最终给浏览器响应数据.

1.1初始化(了解)

DispatcherServlet的初始化⽅法 init() 在其⽗类 HttpServletBean 中实现的
主要作⽤是加载 web.xml 中 DispatcherServlet 的 配置, 并调⽤⼦类的初始化

web.xml是web项⽬的配置⽂件,⼀般的web⼯程都会⽤到web.xml来Listener,Filter,Servlet等, Spring框架从3.1版本开始⽀持Servlet3.0, DispatcherServlet, 实现不再使⽤web.xml

init() 具体代码如下:

java 复制代码
 @Override
    public final void init() throws ServletException {
        try {
            // ServletConfigPropertyValues 是静态内部类,使⽤ ServletConfig 获取
            web.xml 中配置的参数
            PropertyValues pvs = new
                    ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            // 使⽤ BeanWrapper 来构造 DispatcherServlet
            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) {}
        // 让⼦类实现的⽅法,这种在⽗类定义在⼦类实现的⽅式叫做模版⽅法模式
        initServletBean();
    }

在 HttpServletBean 的 init() 中调⽤了 initServletBean() , 它是在
FrameworkServlet 类 中实现的, 主要作⽤是建⽴ WebApplicationContext 容器 (有时也称上下⽂), 并加载 SpringMVC 配置⽂件中定义的 Bean到该容器中, 最后将该容器添加到 ServletContext 中
initServletBean() 的具体代码:

java 复制代码
    /**
     * Overridden method of {@link HttpServletBean}, invoked after any bean 
     properties
     * have been set. Creates this servlet's WebApplicationContext.
     */
    @Override
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring " + getClass().getSimpleName()
                + " '" + getServletName() + "'");
        if (logger.isInfoEnabled()) {
            logger.info("Initializing Servlet '" + getServletName() + "'");
        }
        long startTime = System.currentTimeMillis();
        try {
            //创建ApplicationContext容器
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException | RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            throw ex;
        }
        if (logger.isDebugEnabled()) {
            String value = this.enableLoggingRequestDetails ?
                    "shown which may lead to unsafe logging of potentially sensitive 
            data" :
            "masked to prevent unsafe logging of potentially sensitive data";
            logger.debug("enableLoggingRequestDetails='" +
                    this.enableLoggingRequestDetails +
                    "': request parameters and headers will be " + value);
        }
        if (logger.isInfoEnabled()) {
            logger.info("Completed initialization in " + (System.currentTimeMillis()
                    - startTime) + " ms");
        }
    }

此处打印的⽇志, 也正是控制台打印出来的⽇志:

源码跟踪技巧:
在阅读框架源码的时候, ⼀定要抓住关键点, 找到核⼼流程.
切忌从头到尾⼀⾏⼀⾏代码去看, ⼀个⽅法的去研究, ⼀定要找到关键流程, 抓住关键点, 先在宏观上对整个流程或者整个原理有⼀个认识, 有精⼒再去研究其中的细节

初始化web容器的过程中, 会通过onRefresh 来初始化SpringMVC的容器

java 复制代码
  protected WebApplicationContext initWebApplicationContext() {
        //...
        if (!this.refreshEventReceived) {
            //初始化Spring MVC
            synchronized (this.onRefreshMonitor) {
                onRefresh(wac);
            }
        }
        return wac;
    }
    @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);
        initFlashMapManager(context);
}

在initStrategies()中进⾏9⼤组件的初始化, 如果没有配置相应的组件,就使⽤默认定义的组件(在
DispatcherServlet.properties中有配置默认的策略, ⼤致了解即可)

⽅法initMultipartResolver、initLocaleResolver、init、initRequestToViewNameTranslator、initFlashMapManager的处理⽅式⼏乎都⼀样(1.2.3.7.8,9),从应⽤⽂中取出指定的Bean, 如果没有, 就使⽤默认的.
⽅法initHandlerMappings、initHandlerAdapters、initHandlerExceptionResolvers的处理⽅式⼏乎
都⼀样(4,5,6)

1.初始化⽂件上传解析器MultipartResolver: 从应⽤上下⽂中获取名称为multipartResolver的Bean,如果没有名为multipartResolver的Bean,则没有提供上传⽂件的解析器
2.初始化区域解析器LocaleResolver: 从应⽤上下⽂中获取名称为localeResolver的Bean,如果没有这个Bean,则默认使⽤AcceptHeaderLocaleResolver作为区域解析器
3.初始化主题解析器ThemeResolver: 从应⽤上下⽂中获取名称为themeResolver的Bean,如果没有这个Bean,则默认使⽤FixedThemeResolver作为主题解析器
4.初始化处理器映射器HandlerMappings: 处理器映射器作⽤,1)通过处理器映射器找到对应的处理器适配器,将请求交给适配器处理;2)缓存每个请求地址URL对应的位置(Controller.xxx⽅法);如果在ApplicationContext发现有HandlerMappings,则从ApplicationContext中获取到所有的HandlerMappings,并进⾏排序;如果在ApplicationContext中没有发现有处理器映射器,则默认BeanNameUrlHandlerMapping作为处理器映射器
5.初始化处理器适配器HandlerAdapter: 作⽤是通过调⽤具体的⽅法来处理具体的请求;如果在 ApplicationContext发现有handlerAdapter,则从ApplicationContext中获取到所有的HandlerAdapter,并进⾏排序;如果在ApplicationContext中没有发现处理器适配器,则默认 SimpleControllerHandlerAdapter作为处理器适配器
6.初始化异常处理器解析器HandlerExceptionResolver: 如果在ApplicationContext发现有
handlerExceptionResolver,则从ApplicationContext中获取到所有的HandlerExceptionResolver,并进⾏排序;如果在ApplicationContext中没有发现异常处理器解析器,则不设置异常处理器

7.初始化RequestToViewNameTranslator: 其作⽤是从Request中获取viewName,从ApplicationContext发现有viewNameTranslator的Bean,如果没有,则默认使⽤DefaultRequestToViewNameTranslator
8.初始化视图解析器ViewResolvers: 先从ApplicationContext中获取名为viewResolver的Bean如果没有,则默认InternalResourceViewResolver作为视图解析器
9.初始化FlashMapManager: 其作⽤是⽤于检索和保存FlashMap(保存从⼀个URL重定向到另⼀个URL时的参数信息),从ApplicationContext发现有flashMapManager的Bean,如果没有,则默认使⽤DefaultFlashMapManager

1.2 处理请求(核心)

DispatcherServlet 接收到请求后, 执⾏doDispatch 调度⽅法, 再将请求转给Controller.
我们来看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 {
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;
                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    //1. 获取执⾏链
                    //遍历所有的 HandlerMapping 找到与请求对应的Handler
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    //2. 获取适配器
                    //遍历所有的 HandlerAdapter,找到可以处理该 Handler 的
                    HandlerAdapter
                    HandlerAdapter ha =
                            this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = HttpMethod.GET.matches(method);
                    if (isGet || HttpMethod.HEAD.matches(method)) {
                        long lastModified = ha.getLastModified(request,
                                mappedHandler.getHandler());
                        if ((new ServletWebRequest(request,
                                response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
                    //3. 执⾏拦截器preHandle⽅法
                    if (!mappedHandler.applyPreHandle(processedRequest, response))
                    {
                        return;
                    }

                    //4. 执⾏⽬标⽅法
                    mv = ha.handle(processedRequest, response,
                            mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    this.applyDefaultViewName(processedRequest, mv);
                    //5. 执⾏拦截器postHandle⽅法
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler
                            dispatch failed", var21);
                }
                //6. 处理视图, 处理之后执⾏拦截器afterCompletion⽅法
                this.processDispatchResult(processedRequest, response,
                        mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                //7. 执⾏拦截器afterCompletion⽅法
                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);
            }
        }
    }

HandlerAdapter 在 Spring MVC 中使⽤了适配器模式:
适配器模式, 也叫包装器模式. 简单来说就是⽬标类不能直接使⽤, 通过⼀个新类进⾏包装⼀下, 适配调⽤⽅使⽤.
把两个不兼容的接⼝通过⼀定的⽅式使之兼容。

HandlerAdapter 主要⽤于⽀持不同类型的处理器(如 Controller、HttpRequestHandler 或者
Servlet 等),让它们能够适配统⼀的请求处理流程。这样,Spring MVC 可以通过⼀个统⼀的接⼝来处理来⾃各种处理器的请求。
从上述源码可以看出在开始执⾏ Controller 之前,会先调⽤ 预处理⽅法 applyPreHandle,⽽
applyPreHandle ⽅法的实现源码如下:

java 复制代码
 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse
            response) throws Exception {
        for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex =
                i++) {
            // 获取项⽬中使⽤的拦截器 HandlerInterceptor
            HandlerInterceptor interceptor =
                    (HandlerInterceptor)this.interceptorList.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }
        return true;
    }

在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor , 并执⾏拦截器中的
preHandle ⽅法,这样就会咱们前⾯定义的拦截器对应上了,如下图所⽰:

如果拦截器返回true, 整个发放就返回true, 继续执⾏后。
如果拦截器返回fasle, 则中断后续操作

相关推荐
Theodore_102229 分钟前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸2 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象2 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
没书读了2 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
----云烟----2 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024063 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
小二·3 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic3 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it3 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
懒洋洋大魔王3 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq