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

本文详细解析了DispatcherServlet的初始化过程,介绍了如何通过适配器模式处理请求,包括拦截器的作用以及HandlerAdapter的实现。同时讨论了适配器模式的应用场景,尤其是在SpringMVC中的应用。
摘要由CSDN通过智能技术生成

🌴前言

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

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

🎋了解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()具体代码如下:

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

/**
 * 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的容器

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

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

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

在这里插入图片描述

⽅法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 < 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,则中断后续操作

🍃适配器模式

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

🚩适配器模式的定义

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

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

比如下⾯两个接⼝,本⾝是不兼容的(参数类型不⼀样,参数个数不⼀样等等
在这里插入图片描述
可以通过适配器的⽅式,使之兼容

在这里插入图片描述

🚩适配器模式角色

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

🚩适配器模式应用场景

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

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

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

⭕总结

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

  • 47
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论
JavaEE中的拦截器是一种常见的应用程序组件,它可以在请求到达目标资源之前或之后执行某些操作。以下是JavaEE中使用拦截器的一些方法和步骤: 1. 创建一个类并实现javax.servlet.Filter接口,该接口定义了doFilter()方法,该方法在请求到达目标资源之前或之后执行某些操作。 ```java public class MyFilter implements javax.servlet.Filter { public void init(FilterConfig config) throws ServletException { // 初始化操作 } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 在请求到达目标资源之前执行的操作 chain.doFilter(request, response); // 调用下一个过滤器或目标资源 // 在请求到达目标资源之后执行的操作 } public void destroy() { // 销毁操作 } } ``` 2. 在web.xml文件中配置过滤器,指定拦截的URL模式和拦截器类。 ```xml <filter> <filter-name>MyFilter</filter-name> <filter-class>com.example.MyFilter</filter-class> </filter> <filter-mapping> <filter-name>MyFilter</filter-name> <url-pattern>/user/*</url-pattern> </filter-mapping> ``` 上述配置表示拦截以/user/开头的所有URL请求,并使用MyFilter类进行处理。 3. 在Servlet中使用注解配置拦截器。 ```java @WebServlet("/user") @ServletSecurity(@HttpConstraint(rolesAllowed = {"admin"})) public class MyServlet extends HttpServlet { // Servlet代码 } ``` 上述代码使用@WebServlet注解指定了Servlet的URL模式,使用@ServletSecurity注解指定了需要的安全角色。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

遇事问春风乄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值