Hey, Nice to meet You. 

必有过人之节.人情有所不能忍者,匹夫见辱,拔剑而起,挺身而斗,此不足为勇也,天下有大勇者,猝然临之而不惊,无故加之而不怒.此其所挟持者甚大,而其志甚远也.          ☆☆☆所谓豪杰之士,

SpringMVC入门学习(十二)----SpringMVC的拦截器

1、拦截器介绍

SpringMVC 中的拦截器类似于 Servlet 开发中的过滤器 Filter ,只不过拦截器的功能更为强大。SpringMVC 中的 拦截器是非常重要和相当有用的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。其拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。当然,在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。

拦截器,本质类似于AOP,它主要的应用场景

  • 日志记录:记录请求信息的日志,以便进行信息监控、信息统计等。
  • 权限检查:如登录检测,进入处理器检测是否登录,没有登录返回登录页面。
  • 性能监控:记录拦截器进入处理器和离开处理器的时间。
  • 通用行为:读取cookie中的用户信息放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器的需要都可以使用拦截器实现。

拦截器的三要素:

  1. 拦截
  2. 过滤
  3. 放行

2、拦截器的两种实现方式

  • 实现拦截器处理器接口(推荐):org.springframework.web.servlet.HandlerInterceptor
    • preHandle(HttpServletRequest request,HttpServletResponse response, Object handler):预处理回调方法。在Controller前执行,返回true继续执行下一个流程(interceptor或handler)。返回false中断执行,不会再调用拦截器或处理器。可以用于身份认证、身份授权。比如如果认证没有通过表示用户没有登陆,需要此方法拦截不再往下执行(return false),否则就放行(return true)。
    • postHandle(HttpServletRequest request,HttpServletResponse response, Object handler,ModelAndView modelAndView):后处理回调方法。在进入Controller后,返回ModelAndView之前执行,可以通过对ModeAndView进行处理或对视图进行处理,ModeAndView可能为null。应用场景:从modelAndView出发:将公用的模型数据(比如菜单导航之类的)在这里传到视图,也可以在这里统一指定视图。
    • afterCompletion(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex):整个请求完毕的回调方法。在视图渲染完毕时回调。应用场景:统一异常处理,统一日志处理等。
  • 继承拦截器适配器类:org.springframework.web.servlet.handler.HandlerInterceptorAdapter
    • 实现拦截器需要重写三个接口,拦截器适配器为这三个方法做了空实现,可以继承这个类,根据需要重写拦截器的1~3个方法。

3、拦截器的定义与配置

在 SpringMVC 中,定义拦截器推荐使用实现HandlerInterceptor接口的方式。也就是自定义拦截器必须实现HandlerInterceptor接口,并实现该接口中提供的三个方法,代码如下:

/**
 * 定义一个拦截器
 */
public class HandlerInterceptor1 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截器1的preHandle方法执行了...");
        //返回值true表示放行,false表示不放行
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截器1的postHandle方法执 行了");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("拦截器1的afterCompletion方法执行了");
    }
}

在spring-mvc.xml配置文件中配置拦截器:

  • 在 <mvc:interceptors>:使用 <mvc:interceptor> 标签对拦截器进行作用范围的设置。
  • 使用<mvc:mapping path="" />:设置处理的请求,可以使用通配符,可以配置多个。
  • 使用<mvc:exclude-mapping path="" />:设置不需要拦截的请求,可以使用通配符,可以配置多个。但是使用的前提是需要先配置需要处理的请求范围,即需要先配置了<mvc:mapping path="" />才行,否则会有错误。
<!-- 配置拦截器,拦截器可以有0或多个 -->
<mvc:interceptors>
    <!-- 配置一个拦截器 -->
    <mvc:interceptor>
        <!-- mvc:mapping用于指定当前所注册的拦截器可以拦截的请求路径,path路径/**表示拦截所有请求,两个*表示匹配多级目录URL地址,例如/aaa/bbb/ccc -->
        <mvc:mapping path="/**"/>
        <bean id="handlerInterceptor1" class="com.thr.interceptor.HandlerInterceptor1"/>
    </mvc:interceptor>
</mvc:interceptors>

启动Tomcat测试,随便访问一个请求,查看控制台的打印结果:

image

4、多个拦截器的执行顺序

在 SpringMVC 中是可以配置多个拦截器,它们按照定义的先后顺序执行,但是拦截器中的方法执行顺序却是不一样的,下面来验证一下。分别创建三个拦截器,它们的配置如下:

<!-- 配置拦截器,拦截器可以有0或多个 -->
<mvc:interceptors>
    <!-- 配置一个拦截器 -->
    <mvc:interceptor>
        <!-- mvc:mapping/用于指定当前所注册的拦截器可以拦截的请求路径,url路径/**表示拦截所有请求 -->
        <mvc:mapping path="/**"/>
        <bean id="handlerInterceptor1" class="com.thr.interceptor.HandlerInterceptor1"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean id="handlerInterceptor2" class="com.thr.interceptor.HandlerInterceptor2"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean id="handlerInterceptor3" class="com.thr.interceptor.HandlerInterceptor3"/>
    </mvc:interceptor>
</mvc:interceptors>

启动Tomcat测试,随便访问一个请求,查看控制台的打印结果:

image

可以发现,先是执行了拦截器1、2、3 的preHandle()方法,然后再逆序执行了3个拦截器的postHandle()方法,最后逆序执行拦截器的afterCoompletion()方法。

通过观察分析总结多个拦截器的执行流程如下所示:

image

在SpringMVC中,拦截器是一个链式的,只有前面的拦截器放行时后面的拦截器才能够执行,例如把第一个拦截器的predHandle()方法设置为false。

image

再次访问请求时,由于第一个拦截器不放行,导致后面的都执行不了,所以拦截器何时放行时非常重要的!!!

image

5、拦截器登的录实例

[1]、创建实现登陆的Controller方法

/**
 * 登录Controller
 */
@Controller
public class LoginController {
 
    @RequestMapping(value = "login")
    public String login(String username, String password, HttpServletRequest request){
        //获取session对象
        HttpSession session = request.getSession();
        //模拟登录,实际从数据库获取
        if ("admin".equals(username)&&"123456".equals(password)){
            session.setAttribute("username",username);
            session.setAttribute("password",password);
            return "success";
        }else {
            //登录失败,返回登录界面,重新登录
            session.setAttribute("errorMsg","账号或密码错误!");
            return "redirect:/login.jsp";
        }
    }
}

[2]、登陆验证拦截器的实现

  • 如果用户 session 不存在则跳转到登陆页面
  • 如果用户 session 存在放行,则放行继续操作。
/**
 * 拦截器登录案例
 */
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(request.getContextPath());
        ////获取请求的url
        String uri = request.getRequestURI();
        //判断当前请求地址是否是登录地址
        if (uri.indexOf("login")>0){
            return true;
        }
        //获取session对象
        HttpSession session = request.getSession();
        //判断session中是否有用户身份信息,如果不为空,说明用户已经登录过,放行
        String username = (String) session.getAttribute("username");
        if(username != null) {
            return true;
        }
        //执行到这里表示用户身份需要验证,说明用户之前没有登录过,跳转到登录页面
        //转发
        //request.getRequestDispatcher("login.jsp").forward(request, response);
        //重定向
        response.sendRedirect("login.jsp");
        //默认拦截
        return false;
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}

[3]、配置该拦截器:

<!--  配置登录拦截器  -->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean id="loginInterceptor" class="com.thr.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

[4]、登录页面以及回显的页面

  • 登录页面:login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>登录</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/login" method="post">
    <table style="width: 300px;height: 100px;">
        <tr>
            <td style="text-align: center">用户名:</td>
            <td><input type="text" name="username" id="username"/></td>
        </tr>
        <tr>
            <td style="text-align: center">密  码:</td>
            <td><input type="password" name="password" id="password"/></td>
        </tr>
        <tr>
            <td></td>
            <td><span style="color: red">${errorMsg}</span></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" id="login_button" value="用户登录"/></td>
        </tr>
    </table>
</form>
</body>
</html>
  • 登录成功页面:success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>成功</title>
</head>
<body>
    登录用户:${username}<br>
    登录密码:${password}
</body>
</html>
posted @ 2021-05-21 17:00  唐浩荣  阅读(427)  评论(0编辑  收藏  举报