springmVC源码分析之拦截器

  一个东西用久了,自然就会从仅使用的层面上升到探究其原理的层面,在javaweb中springmvc更是如此,越是优秀的框架,其底层实现代码更是复杂,而在我看来,一个优秀程序猿就相当于一名武林高手,不断进阶武功秘籍,越是高深莫测的功夫,越是要探究其原理,而springmvc就是一本十分深奥的武功秘籍。

  说起拦截器,说不得不和过滤器进行对比,在此贴图一张不进行多加解释,简单的来说拦截器能作用于controller层方法实现的前后。

 

  在这里先列出一个简单的controller层的实现

  

正常访问之后我们看看控制台

  我们都知道DispatcherServlet是所谓前端控制器,是整个Springmvc的入口,但是这个前端控制器里面又有许多门,我们都看过箱子里面装着又一个箱子,跟这种感觉差不多。

  DispatcherServlet里面执行处理入口的方法是doService,先看源码

 protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if(this.logger.isDebugEnabled()) {
            String attributesSnapshot = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult()?" resumed":"";
            this.logger.debug("DispatcherServlet with name \‘" + this.getServletName() + "\‘" + attributesSnapshot + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }      

        HashMap attributesSnapshot1 = null;
        if(WebUtils.isIncludeRequest(request)) {
            attributesSnapshot1 = new HashMap();
            Enumeration inputFlashMap = request.getAttributeNames();

            label108:
            while(true) {
                String attrName;
                do {
                    if(!inputFlashMap.hasMoreElements()) {
                        break label108;
                    }

                    attrName = (String)inputFlashMap.nextElement();
                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot1.put(attrName, request.getAttribute(attrName));
            }
        }

        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
        FlashMap inputFlashMap1 = this.flashMapManager.retrieveAndUpdate(request, response);
        if(inputFlashMap1 != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap1));
        }

        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

        try {
            this.doDispatch(request, response);
        } finally {
            if(!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot1 != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot1);
            }

        }

    }

  由于主要是先分析拦截器,doservice的其他部分就先不解释,先看一段代码

  

this.logger.debug("DispatcherServlet with name \‘" + this.getServletName() + "\‘" + attributesSnapshot + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");

  这段跟我贴出的控制台信息截图的第一段信息是不是很相似,由此可以证明,doService的确是执行处理方法的入口。但是doService并没有直接进行处理,而是交给了doDispatch进行具体的处理。下面的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 err = null;
                Exception dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if(mappedHandler == null || mappedHandler.getHandler() == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                    HandlerAdapter ex = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if(isGet || "HEAD".equals(method)) {
                        long lastModified = ex.getLastModified(request, mappedHandler.getHandler());
                        if(this.logger.isDebugEnabled()) {
                            this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }

                        if((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                    if(!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }

                    err = ex.handle(processedRequest, response, mappedHandler.getHandler());
                    if(asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(request, err);
                    mappedHandler.applyPostHandle(processedRequest, response, err);
                } catch (Exception var19) {
                    dispatchException = var19;
                }

                this.processDispatchResult(processedRequest, response, mappedHandler, err, dispatchException);
            } catch (Exception var20) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
            } catch (Error var21) {
                this.triggerAfterCompletionWithError(processedRequest, response, mappedHandler, var21);
            }

        } finally {
            if(asyncManager.isConcurrentHandlingStarted()) {
                if(mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if(multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

  那么问题来了,既然我发出的请求是转发到doDispatch进行具体请求,那么请求和Controller层之间是怎么联系上的,我们再来看看控制层的信息

  第一段的信息是查找运用在请求的url /a上的方法,第二段即是找到方法并返回,第三段则是找到了Controller,而这一整个过程都是由HandlerMapping进行工作的。没错,虽然拦截器是作用于控制层前后,但我们确实是先找控制层,拦截器再起作用。

  然后找到代码中我们要的信息

 if(!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }

  也是说这部分封装的是控制层执行之前的方法,我们打开这个方法可以看到

 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if(!interceptor.preHandle(request, response, this.handler)) {
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
        }

        return true;
    }

  这个方法先是判断拦截器是否为空,然后用for循环对每个拦截器Intercepter使用preHandler方法,我们先留意到里面一句代码

HandlerInterceptor interceptor = interceptors[i];

  每个拦截器 Intercepter 都必须继承或者实现 HandlerInterceptor,所以这样声明类型是运用到多态。

  我们打开preHandle方法可以看到

  

public interface HandlerInterceptor {
    boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;

    void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}

  自然而然出现的HandlerIntercetor,这是用到多态的只是,超类可以调用子类方法,从而实现解耦以及拓展性。

  处理完preHandle后,就到了执行控制层的方法,处理完之后先进行对view的处理,当view为空时,设置默认view,然后就执行控制层执行之后的方法,也就是postHandle

  

 this.applyDefaultViewName(request, err);
 mappedHandler.applyPostHandle(processedRequest, response, err);

  接着调用postHandle的调用过程也preHandle方法相似,最后使用processDispatchResult方法处理前面返回的结果,其中包括处理异常,渲染页面,触发Interceptor的afterCompletion方法。

  自此我们已经彻底分析完源码当中关于拦截器的代码,在现实当中,经常用的更是自定义拦截器,主要作于于:

   1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。

  2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;

  3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);

  4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。

  5、OpenSessionInView:如Hibernate,在进入处理器打开Session,在完成后关闭Session。

    …………本质也是AOP(面向切面编程),也就是说符合横切关注点的所有功能都可以放入拦截器实现。

  自定义拦截器需要继承或者实现HandlerInterceptorAdapter类,在此贴出记录时间的代码

  

public class StopWatchHandlerInterceptor  extends HandlerInterceptorAdapter{
    private static Logger logger = Logger.getLogger(StopWatchHandlerInterceptor.class);
    private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        startTimeThreadLocal.set(startTime);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        long endTime = System.currentTimeMillis();
        long startTime = startTimeThreadLocal.get();
        String uri = request.getRequestURI();
        logger.info("handle uri:" + uri + " for " + (endTime - startTime) + " ms.");
    }
}

  在dispacher的配置文件加上

<!-- 全局拦截器 -->
    <mvc:interceptors>
        <!--拦截所有请求-->
        <bean class="scau.zzf.interceptor.interceptor.StopWatchHandlerInterceptor"/><mvc:interceptors/>

  

时间: 2025-01-02 01:02:01

springmVC源码分析之拦截器的相关文章

SpringMVC源码阅读:拦截器

1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC拦截器的工作原理 2.源码分析 进入SpringMVC核心类DispatcherServlet的doDispatch方法,在SpringMVC源码阅读:核心分发器DispatcherServlet曾经分析过,这里再分析一遍 936行获得HandlerExec

Spring AOP源码分析(拦截器调用的实现)

在Spring AOP通过JDK或者Cglib的方式生成代理对象的时候,相关的拦截器已经配置到代理对象中去了,拦截器在代理对象中的作用是通过对这些方法的回调完成的. JdkDynamicAopProxy的invoke拦截 JdkDynamicAopProxy的invoke @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation in

SpringMVC源码分析(3)DispatcherServlet的请求处理流程

<SpringMVC源码分析(1)标签解析>:介绍了解析过程中,初始化若干组件. <SpringMVC源码分析(2)DispatcherServlet的初始化>:初始化DispatcherServlet的多个组件. 本文继续分析DispatcherServlet解析请求的过程. 概览 ①:DispatcherServlet是springmvc中的前端控制器(front controller),负责接收request并将request转发给对应的处理组件. ②:HanlerMappi

【转】springmvc源码分析链接

SpringMVC源码 SpringMVC源码分析系列 说到java的mvc框架,struts2和springmvc想必大家都知道,struts2的设计基本上完全脱离了Servlet容器,而 springmvc是依托着Servlet容器元素来设计的,同时springmvc基于Spring框架,Spring框架想必搞java的同学都很熟 悉. 一进Spring的官网就发现了这样一排醒目的文字, spring可以让我们构造简单的.便携的.又快又易于扩展的基于jvm的系统和应用程序. 没错,基于Spr

SpringMVC源码分析系列

说到java的mvc框架,struts2和springmvc想必大家都知道,struts2的设计基本上完全脱离了Servlet容器,而springmvc是依托着Servlet容器元素来设计的,同时springmvc基于Spring框架,Spring框架想必搞java的同学都很熟悉. 一进Spring的官网就发现了这样一排醒目的文字, spring可以让我们构造简单的.便携的.又快又易于扩展的基于jvm的系统和应用程序. 没错,基于Spring的MVC框架SpringMVC同样也可以构造具有这些特

SpringMVC源码分析(5)剖析重要组件HandlerMapping

<SpringMVC源码分析(4)剖析DispatcherServlet重要组件>   简要介绍了各个组件,从本章开始,针对各个组件,深入介绍.首先是HandlerMapping. HanlerMapping是沟通请求和后端controller映射,是所有请求的入口. 1.类结构介绍 该图只对属性和类层级进行了描述,屏蔽了方法,主要是为了根据内部属性,重点理解Spring HandlerMapping提供功能. 1.1 AbstractHandlerMapping HandlerMapping

SpringMVC源码分析(6)剖析DefaultAnnotationHandlerMapping

接<SpringMVC源码分析(5)剖析重要组件HandlerMapping>,继续剖析HandlerMapping,DefaultAnnotationHandlerMapping是SpringMVC 中最重要的HandlerMapping组件.虽然它在spring3.1版本后被废弃了. 包括2部分内容 DefaultAnnotationHandlerMapping剖析 HandlerMapping的拦截器 1.DefaultAnnotationHandlerMapping剖析 鉴于它的重要地

7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解

从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()) 这样的方式来执行request的handler方法. 先来分析一下ha.handle方法的调用过程:HandlerAdapter接口有一个抽象实现类AbstractHandlerMethodAdapter,在该抽象类中通过具体方法

springmvc源码分析系列-请求处理流程

接上一篇-springmvc源码分析开头片 上一节主要说了一下springmvc与struts2的作为MVC中的C(controller)控制层的一些区别及两者在作为控制层方面的一些优缺点.今天就结合下面的一张图和上一篇中关于springmvc各个模块之间及各个模块中的类的继承关系的一张图对springmvc的请求处理流程进行一个分析.当然有了springmvc的请求处理流程我们就知道了springmvc是如何在启动的时候去加载或者去解析对应的具体控制器,以及modleAndView使干什么用的