🌴前言
上一篇博客我们使用了拦截器,那么拦截器是如何实现拦截的呢?
接下来我们将从源码来看一下是如何实现拦截的。
🎋了解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)
- 初始化⽂件上传解析器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⽅法的具体实现
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)源码简介》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!