1、拦截器介绍
SpringMVC 中的拦截器类似于 Servlet 开发中的过滤器 Filter ,只不过拦截器的功能更为强大。SpringMVC 中的 拦截器是非常重要和相当有用的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。其拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。当然,在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。
拦截器,本质类似于AOP,它主要的应用场景:
- 日志记录:记录请求信息的日志,以便进行信息监控、信息统计等。
- 权限检查:如登录检测,进入处理器检测是否登录,没有登录返回登录页面。
- 性能监控:记录拦截器进入处理器和离开处理器的时间。
- 通用行为:读取cookie中的用户信息放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器的需要都可以使用拦截器实现。
拦截器的三要素:
- 拦截
- 过滤
- 放行
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测试,随便访问一个请求,查看控制台的打印结果:
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测试,随便访问一个请求,查看控制台的打印结果:
可以发现,先是执行了拦截器1、2、3 的preHandle()方法,然后再逆序执行了3个拦截器的postHandle()方法,最后逆序执行拦截器的afterCoompletion()方法。
通过观察分析总结多个拦截器的执行流程如下所示:
在SpringMVC中,拦截器是一个链式的,只有前面的拦截器放行时后面的拦截器才能够执行,例如把第一个拦截器的predHandle()方法设置为false。
再次访问请求时,由于第一个拦截器不放行,导致后面的都执行不了,所以拦截器何时放行时非常重要的!!!
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=" 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>