10. 拦截器

空~2022年9月15日
  • SpringMVC
大约 7 分钟

10. 拦截器

SpringMVC 中的 Interceptor 拦截器是非常重要和相当有用的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理

自定义拦截器,需要实现 HandlerInterceptor 接口。而该接口中含有三个方法:

preHandle(request,response, Object handler):

该方法在处理器方法执行之前执行。

其返回值为 boolean,若为 true,则紧接着会执行处理器方法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行

postHandle(request,response, Object handler,modelAndView)**:**

该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。

由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向

afterCompletion(request,response, Object handler, Exception ex)**:**

当 preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。

即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此时对 ModelAndView 再操作也对响应无济于事

afterCompletion 最后执行的方法,清除资源,例如在 Controller 方法中加入数据.

拦截器中方法与处理器方法的执行顺序如下图:

拦截器

一个拦截器的执行

//拦截器类:拦截用户的请求。
public class MyInterceptor implements HandlerInterceptor {
    private long btime = 0;
    /*
     * preHandle叫做预处理方法。
     * 重要:是整个项目的入口,门户。 当preHandle返回true 请求可以被处理。
     *        preHandle返回false,请求到此方法就截止。
     * 参数:
     *  Object handler : 被拦截的控制器对象
     * 返回值boolean
     *   true:请求通过了拦截器的验证,可以执行处理器方法。
         *   拦截器的MyInterceptor的preHandle()
             =====执行MyController中的doSome方法=====
             拦截器的MyInterceptor的postHandle()
             拦截器的MyInterceptor的afterCompletion()
         *
     *   false:请求没有通过拦截器的验证,请求到达拦截器就截止了。 请求没有被处理
     *      拦截器的MyInterceptor的preHandle()
     *  特点:
     *   1.方法在控制器方法(MyController的doSome)之前先执行的。
     *     用户的请求首先到达此方法
     *
     *   2.在这个方法中可以获取请求的信息, 验证请求是否符合要求。
     *     可以验证用户是否登录, 验证用户是否有权限访问某个连接地址(url)。
     *     如果验证失败,可以截断请求,请求不能被处理。
     *     如果验证成功,可以放行请求,此时控制器方法才能执行。
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        btime = System.currentTimeMillis();
        System.out.println("拦截器的MyInterceptor的preHandle()");
        // 计算的业务逻辑,根据计算结果,返回true或者false
        // 可以给浏览器一个返回结果
        //request.getRequestDispatcher("/tips.jsp").forward(request,response);
        return true;
    }

    /*
       postHandle:后处理方法。
       参数:
        Object handler:被拦截的处理器对象MyController
        ModelAndView mv:处理器方法的返回值

        特点:
         1.在处理器方法之后执行的(MyController.doSome())
         2.能够获取到处理器方法的返回值ModelAndView,可以修改ModelAndView中的
         数据和视图,可以影响到最后的执行结果。
         3.主要是对原来的执行结果做二次修正,

         ModelAndView mv = MyController.doSome();
         postHandle(request,response,handler,mv);
     */
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler, ModelAndView mv) throws Exception {
        System.out.println("拦截器的MyInterceptor的postHandle()");
        //对原来的doSome执行结果,需要调整。
        if( mv != null){
            //修改数据
            mv.addObject("mydate",new Date());
            //修改视图
            mv.setViewName("other");
        }
    }

    /*
      afterCompletion:最后执行的方法
      参数
        Object handler:被拦截器的处理器对象
        Exception ex:程序中发生的异常
      特点:
       1.在请求处理完成后执行的。框架中规定是当你的视图处理完成后,对视图执行了forward。就认为请求处理完成。
       2.一般做资源回收工作的, 程序请求过程中创建了一些对象,在这里可以删除,把占用的内存回收。
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        System.out.println("拦截器的MyInterceptor的afterCompletion()");

        long etime = System.currentTimeMillis();
        System.out.println("计算从preHandle到请求处理完成的时间:"+(etime - btime ));
    }
}

在 springmv 配置文件中声明拦截器

<!--声明拦截器: 拦截器可以有0或多个
        在框架中保存多个拦截器是ArrayList,
        按照声明的先后顺序放入到ArrayList
    -->
<mvc:interceptors>
  <!--声明第一个拦截器-->
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <!--声明拦截器对象-->
    <bean class="com.bjpowernode.handler.MyInterceptor" />
  </mvc:interceptor>
</mvc:interceptors>

多个拦截器的执行

  • MyInterceptor
//拦截器类:拦截用户的请求。
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("111111-拦截器的MyInterceptor的preHandle()");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler, ModelAndView mv) throws Exception {
        System.out.println("111111-拦截器的MyInterceptor的postHandle()");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        System.out.println("111111-拦截器的MyInterceptor的afterCompletion()");
    }
}
  • MyInterceptor2
//拦截器类:拦截用户的请求。
public class MyInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("22222-拦截器的MyInterceptor的preHandle()");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler, ModelAndView mv) throws Exception {
        System.out.println("22222-拦截器的MyInterceptor的postHandle()");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        System.out.println("22222-拦截器的MyInterceptor的afterCompletion()");
    }
}
  • 声明拦截器
<mvc:interceptors>
  <!--声明第一个拦截器-->
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <!--声明拦截器对象-->
    <bean class="com.bjpowernode.handler.MyInterceptor" />
  </mvc:interceptor>
  <!--声明第二个拦截器-->
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="com.bjpowernode.handler.MyInterceptor2" />
  </mvc:interceptor>
</mvc:interceptors>

当有多个拦截器时,形成拦截器链。

拦截器链的执行顺序,与其注册顺序一致。

提示

需要再次强调一点的是,当某一个拦截器的 preHandle()方法返回 true 并被执行到时,会向一个专门的方法栈中放入该拦截器的 afterCompletion()方法

多个拦截器中方法与处理器方法的执行顺序如下图:

多个拦截器

只要有一个 preHandle()方法返回 false,则上部的执行链将被断开,其后续的处理器方法与 postHandle()方法将无法执行。

但无论执行链执行情况怎样,只要方法栈中有方法,即执行链中只要有 preHandle()方法返回 true,就会执行方法栈中的 afterCompletion()方法。最终都会给出响应。

权限拦截器举例

只有经过登录的用户方可访问处理器,否则,将返回“无权访问”提示。

本例的登录,由一个 JSP 页面完成。即在该页面里将用户信息放入 session 中。

也就是说,只要访问过该页面,就说明登录了。

没访问过,则为未登录用户。

login.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
  <html>
    <head>
      <title>Title</title>
    </head>
    <body>
      模拟登录,zs登录系统
      <%
         session.setAttribute("name","zs");
      %>
     </body>
  </html>

loginout.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
  <html>
    <head>
      <title>Title</title>
    </head>
    <body>
      退出系统,从session中删除数据
      <%
         session.removeAttribute("name");
       %>
     </body>
  </html>

tips.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
  <html>
    <head>
      <title>Title</title>
    </head>
    <body>
      tips.jsp  非zs不能访问系统
    </body>
  </html>

在 springmvc.xml 中声明拦截器对象

<mvc:interceptors>
  <!--声明第一个拦截器-->
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <!--声明拦截器对象-->
    <bean class="com.bjpowernode.handler.MyInterceptor" />
  </mvc:interceptor>
</mvc:interceptors>

controller.java:

@Controller
public class MyController {
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name,Integer age)  {
        System.out.println("=====执行MyController中的doSome方法=====");
        ModelAndView mv  = new ModelAndView();
        mv.addObject("myname",name);
        mv.addObject("myage",age);
        mv.setViewName("show");
        return mv;
    }
}

MyInterceptor.java:

public class MyInterceptor implements HandlerInterceptor {
    //验证登录的用户信息, 正确return true,其它return false
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String loginName = "";
        //从session中获取name的值
        Object attr  = request.getSession().getAttribute("name");
        if( attr != null){
            loginName = (String)attr;
        }
        //判断登录的账户,是否符合要求
        if( !"zs".equals(loginName)){
            // 不能访问系统
            // 给用户提示
            request.getRequestDispatcher("/tips.jsp").forward(request,response);
            return false;
        }
        // zs登录
        return true;
    }
}