小工具知识网

当前位置: 首页 > 行业知识 > 【JavaEE进阶】 拦截器(DispatcherServlet)源码简介

行业知识

【JavaEE进阶】 拦截器(DispatcherServlet)源码简介

2026-11-03 01:33:14 来源:互联网转载

文章目录

  • 前言
  • 了解DispatcherServlet源码
    • 初始化
    • 处理请求
    • 适配器模式
      • 适配器模式的定义
      • 适配器模式角色
      • 适配器模式应用场景
      • ���总结

        前言

        上一篇博客我们使用了拦截器,那么拦截器是如何实现拦截的呢?

        接下来我们将从源码来看一下是如何实现拦截的。

        了解DispatcherServlet源码

        当我们启动服务,进行访问时,我们查看日志,可以看到如下情况

        当Tomcat启动之后,有⼀个核心的类DispatcherServlet,它来控制程序的执行顺序.

        所有请求都会先进到DispatcherServlet,执行doDispatch调度⽅法.

        如果有拦截器,会先执⾏拦截器preHandle() 方法的代码,如果 preHandle() 返回true,继续访问controller中的⽅法.

        controller当中的⽅法执⾏完毕后,再回过来执行 postHandle() 和afterCompletion() ,返回给DispatcherServlet,最终给浏览器响应数据

        初始化

        DispatcherServlet的初始化⽅法,init()在其类HttpServletBean中实现的.

        主要作⽤是加载web.xml中DispatcherServlet的配置,并调用子类的初始化.

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

        init()具体代码如下:

        @Overridepublic 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()的具体代码

        /** * Overridden method of {@link HttpServletBean}, invoked after any bean properties * have been set. Creates this servlet's WebApplicationContext. */@Overrideprotected 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的容器

        protected WebApplicationContext initWebApplicationContext() {//...    if (!this.refreshEventReceived) {//初始化Spring MVC        synchronized (this.onRefreshMonitor) {            onRefresh(wac);        }    }    return wac;}

        在initStrategies()中进⾏9⼤组件的初始化,如果没有配置相应的组件,就使⽤默认定义的组件

        @Overrideprotected void onRefresh(ApplicationContext context) {    initStrategies(context);}/** * Initialize the strategy objects that this servlet uses. * 

        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);}

        ⽅法initMultipartResolver、initLocaleResolver、initThemeResolver、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

        处理请求

        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⽅法的实现源码如下:

          boolean applyPreHandle(HttpServletRequest request, HttpServletResponse        response) throws Exception {    for(int i = 0; i  

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

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

            适配器模式

            HandlerAdapter在Spring MVC中使⽤了适配器模式

            适配器模式的定义

            适配器模式,也叫包装器模式.将⼀个类的接⼝,转换成客⼾期望的另⼀个接⼝,适配器让原本接⼝不兼容的类可以合作⽆间.

            简单来说就是⽬标类不能直接使⽤,通过⼀个新类进⾏包装⼀下,适配调⽤⽅使⽤.把两个不兼容的接⼝通过⼀定的⽅式使之兼容.

            比如下⾯两个接⼝,本⾝是不兼容的(参数类型不⼀样,参数个数不⼀样等等

            可以通过适配器的⽅式,使之兼容

            适配器模式角色

            • Target:⽬标接⼝(可以是抽象类或接⼝),客⼾希望直接⽤的接⼝
            • Adaptee:适配者,但是与Target不兼容
            • Adapter:适配器类,此模式的核⼼.通过继承或者引⽤适配者的对象,把适配者转为⽬标接⼝
            • client:需要使⽤适配器的对象

              适配器模式应用场景

              ⼀般来说,适配器模式可以看作⼀种"补偿模式",⽤来补救设计上的缺陷.

              应⽤这种模式算是"⽆奈之举",如果在设计初期,我们就能协调规避接⼝不兼容的问题,就不需要使⽤适配器模式了

              所以适配器模式更多的应⽤场景主要是对正在运⾏的代码进⾏改造,并且希望可以复⽤原有代码实现新的功能.⽐如版本升级等

              ⭕总结

              关于《【JavaEE进阶】 拦截器(DispatcherServlet)源码简介》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

上一篇:宝塔面板数据库恢复全攻略,轻松找回丢失的数据!

下一篇:PyTorch模型安卓部署流程(NCNN)