0


【JavaEE进阶】 拦截器(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()具体代码如下:

@Overridepublicfinalvoidinit()throwsServletException{try{// ServletConfigPropertyValues 是静态内部类,使⽤ ServletConfig 获取
        web.xml 中配置的参数
        PropertyValues pvs =newServletConfigPropertyValues(getServletConfig(),this.requiredProperties);// 使⽤ BeanWrapper 来构造 DispatcherServletBeanWrapper bw =PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader =newServletContextResourceLoader(getServletContext());
        bw.registerCustomEditor(Resource.class,newResourceEditor(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.
 */@OverrideprotectedfinalvoidinitServletBean()throwsServletException{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 tounsafe 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的容器

protectedWebApplicationContextinitWebApplicationContext(){//...if(!this.refreshEventReceived){//初始化Spring MVCsynchronized(this.onRefreshMonitor){onRefresh(wac);}}return wac;}

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

@OverrideprotectedvoidonRefresh(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.
 */protectedvoidinitStrategies(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⽅法的具体实现

protectedvoiddoDispatch(HttpServletRequest request,HttpServletResponse
        response)throwsException{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 的HandlerAdapterHandlerAdapter 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((newServletWebRequest(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 =newNestedServletException("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,newNestedServletException("Handler processing failed", var23));}}finally{if(asyncManager.isConcurrentHandlingStarted()){if(mappedHandler !=null){
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}elseif(multipartRequestParsed){this.cleanupMultipart(processedRequest);}}}
  • HandlerAdapter在Spring MVC中使⽤了适配器模式,下⾯博主会详细再介绍适配器模式,也叫包装器模式.简单来说就是⽬标类不能直接使⽤,通过⼀个新类进⾏包装⼀下,适配调⽤⽅使⽤(类似转换头).
  • 把两个不兼容的接⼝通过⼀定的⽅式使之兼容.HandlerAdapter主要⽤于⽀持不同类型的处理器(如Controller、HttpRequestHandler或者Servlet等),让它们能够适配统⼀的请求处理流程。这样,Spring MVC可以通过⼀个统⼀的接⼝来处理来⾃各种处理器的请求

从上述源码可以看出在开始执⾏Controller之前,会先调⽤预处理⽅法applyPreHandle,⽽applyPreHandle⽅法的实现源码如下:

booleanapplyPreHandle(HttpServletRequest request,HttpServletResponse
        response)throwsException{for(int i =0; i <this.interceptorList.size();this.interceptorIndex =
            i++){// 获取项⽬中使⽤的拦截器 HandlerInterceptorHandlerInterceptor interceptor =(HandlerInterceptor)this.interceptorList.get(i);if(!interceptor.preHandle(request, response,this.handler)){this.triggerAfterCompletion(request, response,(Exception)null);returnfalse;}}returntrue;}

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

在这里插入图片描述

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

🍃适配器模式

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

🚩适配器模式的定义

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

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

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

在这里插入图片描述

🚩适配器模式角色

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

🚩适配器模式应用场景

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

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

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

⭕总结

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

标签: java-ee hive java

本文转载自: https://blog.csdn.net/m0_71731682/article/details/136302690
版权归原作者 遇事问春风乄 所有, 如有侵权,请联系我们删除。

“【JavaEE进阶】 拦截器(DispatcherServlet)源码简介”的评论:

还没有评论