SpringMVC拦截器详解

本文作者:钟昕灵,叩丁狼高级讲师。原创文章,转载请注明出处。

前言

Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的SpringMVC框架或集成其他MVC开发框架,如Struts1(现在一般不用),Struts2(一般老项目使用)等。

SpringMVC中的Interceptor拦截器用于拦截Controller层接口,表现形式有点像Spring的AOP,但是AOP是针对单一的方法。Interceptor是针对Controller接口以及可以处理request和response对象。

下面,我们来看看SpringMVC中拦截器的使用及实现

HandlerInterceptor接口的定义

在该接口中,定义了一下三个方法

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

  • preHandle:
    在访问到达Controller之前执行,如果需要对请求做预处理,可以选择在该方法中完成
    返回值为true:继续执行后面的拦截器或者Controller
    返回值为false:不再执行后面的拦截器和Controller,并调用返回true的拦截器的afterCompletion方法

  • postHandle:
    在执行完Controller方法之后,渲染视图之前执行,如果需要对响应相关的数据进行处理,可以选择在该方法中完成

  • afterCompletion:
    调用完Controller接口,渲染View页面后调用。返回true的拦截器都会调用该拦截器的afterCompletion方法,顺序相反。

自定义拦截器和使用

自定义一个我们自己的拦截器非常简单,定义一个类,实现上面的接口,然后覆写对应的方法即可
当然,在实际开发中,我们一般选择继承该接口的实现类HandlerInterceptorAdapter来实现拦截器的定义,如下:

public class CheckLoginInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //1:获取登录凭证
        Employee emp  = UserContext.getCurrentUser();
        if(emp == null){
            //没有登录
            response.sendRedirect("/login.html");
            return false; //终止请求继续往下执行
        }
        return true; //放行
    }
}

上面,我们定义了一个检查用户是否登录的拦截器,如果没有登录,跳转到登录页面,反之,放行继续访问目标资源

要让我们的拦截器被框架得知并管理,我们还需要在配置文件中做如下配置来注册拦截器

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/login.do"/>
            <mvc:exclude-mapping path="/login.html"/>
            <bean class="cn.wolfcode.rbac.web.interceptor.CheckLoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

如此,拦截器就能够在项目中部署起来

当用户发起请求相应资源的时候,会首先经过该拦截器的处理,防止用户在没有登录的情况下直接访问项目中的核心资源

原理解析

可以看出,拦截器在SpringMVC框架中实现是非常简单的,但是,大家一定要清楚一个道理

当你觉得很轻松的时候,是有另外一些人替你负重前行

这个时候,是谁在为我们负重前行呢?当然是我们使用的SpringMVC框架了!

那么,框架这个时候都为我们做了哪些事情呢?请往下看:

SpringMVC框架的入口是一个使用Servlet实现的前端控制器:

  • DispatcherServlet

我们的每次请求都会先经过这个入口的处理才能到达目标资源,所以,来看看这里都做了哪些事吧

该类中,最重要的一个方法是doDispatch,在这个方法中,完成了整个执行流程的任务分配

下面是该方法中的部分核心代码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

            try {
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                //返回 HandlerExecutionChain  其中包含了拦截器队列
                mappedHandler = this.getHandler(processedRequest);

                if(mappedHandler == null || mappedHandler.getHandler() == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }

                //获取到适合处理当前请求的适配器,最终用来调用Controller中的方法
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

                //调用拦截器链中所有拦截器的preHandle方法
                if(!mappedHandler.applyPreHandle(processedRequest, response)) {
                    //如果有拦截器的preHandle方法返回值为false,则结束该方法的执行
                    return;
                }
                 //调用请求的Controller中的方法,获取到ModelAndView对象
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                 //调用拦截器链中所有拦截器的postHandle方法,和执行preHandle方法的顺序相反
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var19) {
                dispatchException = var19;
            }
            //处理视图渲染
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        } catch (Exception var20) {
            //如果在执行过程中有异常,执行后续的收尾工作,执行对应拦截器中的afterCompletion方法
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
        } 
    }

  1. mappedHandler = this.getHandler(processedRequest);
    返回 HandlerExecutionChain 其中包含了拦截器队列

  2. HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
    获取到适合处理当前请求的适配器,最终用来调用Controller中的方法

  3. mappedHandler.applyPreHandle(processedRequest, response)
    调用拦截器链中所有拦截器的preHandle方法

  4. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    调用请求的Controller中的方法,获取到ModelAndView对象

  5. mappedHandler.applyPostHandle(processedRequest, response, mv);
    调用拦截器链中所有拦截器的postHandle方法,和执行preHandle方法的顺序相反

  6. this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    处理结果视图的渲染,简单说就是页面的跳转问题

  7. this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
    如果在执行过程中有异常,执行后续的收尾工作,执行对应拦截器中的afterCompletion方法

通过上面对DispatcherServlet中核心代码的分析,相信大家对拦截器的执行流程有了大致的理解

下面我们再对这个过程中的细节继续进行分析:

  • 获取拦截器
    DispatcherServlet:

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
          HandlerExecutionChain handler;
              handler = hm.getHandler(request);
          return handler;
      }
    
    

    AbstractHandlerMapping:

    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
          Object handler = this.getHandlerInternal(request);
          if(handler == null) {
              handler = this.getDefaultHandler();
          }
          if(handler == null) {
              return null;
          } else {
              HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
              return executionChain;
          }
      }
    
    

    AbstractHandlerMapping:
    遍历所有的拦截器, 把所有匹配当前请求的所有拦截添加到拦截器队列中

      protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
          HandlerExecutionChain chain = handler instanceof HandlerExecutionChain?(HandlerExecutionChain)handler:new HandlerExecutionChain(handler);
          String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
          Iterator var5 = this.adaptedInterceptors.iterator();
          while(var5.hasNext()) {
              HandlerInterceptor interceptor = (HandlerInterceptor)var5.next();
              if(interceptor instanceof MappedInterceptor) {
                  MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
                  if(mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                      chain.addInterceptor(mappedInterceptor.getInterceptor());
                  }
              } else {
                  chain.addInterceptor(interceptor);
              }
          }
    
          return chain;
      }
    
    

    MappedInterceptor:
    <mvc:exclude-mapping path="/login.html"/> 如果请求资源路径为 /login.html 则排除当前拦截器
    <mvc:mapping path="/"/>如果请求资源路径为 不在exclude-mapping中,且能够匹配 / 路径, 则添加到拦截器队列

    public boolean matches(String lookupPath, PathMatcher pathMatcher) {
          PathMatcher pathMatcherToUse = this.pathMatcher != null?this.pathMatcher:pathMatcher;
          String[] var4;
          int var5;
          int var6;
          String pattern;
          if(this.excludePatterns != null) {
              var4 = this.excludePatterns;
              var5 = var4.length;
    
              for(var6 = 0; var6 < var5; ++var6) {
                  pattern = var4[var6];
                  if(pathMatcherToUse.match(pattern, lookupPath)) {
                      return false;
                  }
              }
          }
    
          if(this.includePatterns == null) {
              return true;
          } else {
              var4 = this.includePatterns;
              var5 = var4.length;
    
              for(var6 = 0; var6 < var5; ++var6) {
                  pattern = var4[var6];
                  if(pathMatcherToUse.match(pattern, lookupPath)) {
                      return true;
                  }
              }
    
              return false;
          }
      }
    
    
  • 处理拦截器

    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];
                  //调用拦截器中的preHandle方法
                  if(!interceptor.preHandle(request, response, this.handler)) {
                      // 如果preHandler方法返回false,则触发afterCompletion方法的执行
                      this.triggerAfterCompletion(request, response, (Exception)null);
                      return false;
                  }
              }
          }
    
          return true;
      }
    
    
      void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
          HandlerInterceptor[] interceptors = this.getInterceptors();
          if(!ObjectUtils.isEmpty(interceptors)) {
              for(int i = interceptors.length - 1; i >= 0; --i) {
                  HandlerInterceptor interceptor = interceptors[i];
                  //调用拦截器中的postHandle方法
                  interceptor.postHandle(request, response, this.handler, mv);
              }
          }
    
      }
    
    

    当前拦截器中preHandle方法如果返回true,则该方法会在下面几种情况的时候会执行

①Controller正常执行,视图渲染后
②程序有异常的时候
③在任何拦截器preHandle方法返回false的时候

   void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = this.interceptorIndex; i >= 0; --i) {
                HandlerInterceptor interceptor = interceptors[i];
                try {
                    //调用拦截器中的afterCompletion方法
                    interceptor.afterCompletion(request, response, this.handler, ex);
                } catch (Throwable var8) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
                }
            }
        }

    }

可以看到,SpringMVC在执行这一系列的处理的时候,做了很多的细节处理,但是,在我们看源码的时候最好能够排除和功能无关的代码,这样有利于我们理解整个执行流程

所以,在上面的代码中,我仅仅将这个过程中比较重要的代码贴了出来, 不是完整的代码,如果要看完成的代码,请自行参考框架的源码学习,谢谢

想获取更多技术视频,请前往叩丁狼官网:http://www.wolfcode.cn/openClassWeb_listDetail.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容