文章目录
🌴前言
上一篇博客我们使用了拦截器,那么拦截器是如何实现拦截的呢?
接下来我们将从源码来看一下是如何实现拦截的。
🎋了解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)
- 初始化⽂件上传解析器MultipartResolver:从应⽤上下⽂中获取名称为multipartResolver的Bean,如果没有名为multipartResolver的Bean,则没有提供上传⽂件的解析器
- 初始化区域解析器LocaleResolver:从应⽤上下⽂中获取名称为localeResolver的Bean,如果没有这个Bean,则默认使⽤AcceptHeaderLocaleResolver作为区域解析器
- 初始化主题解析器ThemeResolver:从应⽤上下⽂中获取名称为themeResolver的Bean,如果没有这个Bean,则默认使⽤FixedThemeResolver作为主题解析器
- 初始化处理器映射器HandlerMappings:处理器映射器作⽤,1)通过处理器映射器找到对应的处理器适配器,将请求交给适配器处理;2)缓存每个请求地址URL对应的位置(Controller.xxx ⽅法);如果在ApplicationContext发现有HandlerMappings,则从ApplicationContext中获取到所有的HandlerMappings,并进⾏排序;如果在ApplicationContext中没有发现有处理器映射器,则默认BeanNameUrlHandlerMapping作为处理器映射器
- 初始化处理器适配器HandlerAdapter:作⽤是通过调⽤具体的⽅法来处理具体的请求;如果在ApplicationContext发现有handlerAdapter,则从ApplicationContext中获取到所有的HandlerAdapter,并进⾏排序;如果在ApplicationContext中没发现处理器适配器,则默认SimpleControllerHandlerAdapter作为处理器适配器
- 初始化异常处理器解析器HandlerExceptionResolver:如果在ApplicationContext发现有handlerExceptionResolver,则从ApplicationContext中获取到所有的HandlerExceptionResolver,并进⾏排序;如果在ApplicationContext中没有发现异常处理器解 析器,则不设置异常处理器
- 初始化RequestToViewNameTranslator:其作⽤是从Request中获取viewName,从ApplicationContext发现有viewNameTranslator的Bean,如果没有,则默认使⽤ DefaultRequestToViewNameTranslator
- 初始化视图解析器ViewResolvers:先从ApplicationContext中获取名为viewResolver的Bean,如果没有,则默认InternalResourceViewResolver作为视图解析器
- 初始化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)源码简介》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!
版权归原作者 遇事问春风乄 所有, 如有侵权,请联系我们删除。