springMVC中的拦截器
一、拦截器的作用
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。
【拦截器链】(Interceptor Chain): 将拦截器按一定的顺序连接成一条链。在访问被拦截的方法或者字段时,拦截器链中的拦截器就会按照之前定义的顺序被调用。
过滤器与拦截器的区别
拦截器是AOP思想的具体应用。
过滤器 | 拦截器 |
---|---|
servlet规范中的一部分,任何java web工程都可以使用 | 拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用 |
在url-pattern中配置了/* 之后,可以对所有要访问的资源进行拦截 |
拦截器只会拦截访问的控制器方法, 如果访问的是jsp/html/css/image/js是不会进行拦截的 |
二、自定义拦截器的步骤
想要自定义拦截器,必须实现
HandlerInterceptor
接口。
1). 编写一个普通类实现HandlerInterceptor接口
/** 自定义拦截器 */
public class HanderInterceptorOne implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle拦截器拦截了");
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) {
System.out.println("postHandle方法执行了");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
System.out.println("afterCompletion方法执行了");
}
}
2). 配置拦截器
配置springMVC.xml
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="handlerInterceptorOne"
class="com.itheima.myInterceptor.HanderInterceptorOne"/>
</mvc:interceptor>
</mvc:interceptors>
3). 【*】测试运行结果【下面所有测试的运行的代码都使用这一套】
- 访问的index.jsp
<%-- 测试拦截器 --%>
<a href="${pageContext.request.contextPath}/interceptor/function">拦截器测试</a>
- 控制器代码
/** 测试拦截器的控制器 */
@Controller
@RequestMapping("/interceptor")
public class InterceptorController {
@RequestMapping("/function")
public String testFunction() {
System.out.println("控制器中的方法执行了");
return "success";
}
}
- 运行结果
preHandle拦截器拦截了
控制器中的方法执行了
postHandle方法执行了
afterCompletion方法执行了
三、拦截器的细节
1). 拦截器的放行
放行的含义是指,如果有下一个拦截器就执行下一个,如果该拦截器处于拦截器链的最后一个,则执行控制器中的方法。
2). 拦截器中方法的说明
拦截器HandlerInterceptor接口中的三个方法详解。
1. boolean preHandle
方法
- 方法源码
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
如何调用:按照拦截器定义顺序调用
何时调用:只要配置了都会被调用
逻辑功能:如果开发者决定该拦截器对请求拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true。如果开发者决定不需要再调用其他的组件去处理请求,则返回false。
2. void postHandle
方法
- 方法源码
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {}
如何调用:按照拦截器定义逆序调用
何时调用:拦截器链内所有拦截器返回成功时调用
逻辑功能:在业务处理器处理完请求后,但是DispatcherServlet向客户端返回响应前被调用,在该方法中对用户请求request进行处理?
3. void afterCompletion
方法
- 方法源码
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {}
如何调用:按拦截器定义逆序调用
何时调用:只有对应的preHandle返回true才会被调用
逻辑功能:在DispatcherServlet完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
3). 拦截器的作用路径
作用路径可以通过在配置文件中配置。可以配置多个
<!-- 配置拦截器的作用范围 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
-- 用于指定拦截的url,可以配置多个
<mvc:exclude-mapping path="/xxx"/>
-- 用于指定排除的url,可以配置多个
<bean id="handlerInterceptorOne"
class="com.itheima.myInterceptor.HanderInterceptorOne"/>
</mvc:interceptor>
</mvc:interceptors>
4). 多个拦截器的执行顺序
多个拦截器是按照配置的顺序决定的,下面以先后配置
HandlerInterceptorDemo1
/HandlerInterceptorDemo2
为例:
四、正常流程测试案例
1). 拦截器1代码
public class HandlerInterceptorDemo1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("拦截器1:preHandle拦截器拦截了");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("拦截器1:postHandle方法在DispatcherServlet响应前执行了");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("拦截器1:afterCompletion方法在DispatcherServlet响应后执行了");
}
}
2). 拦截器2代码
将拦截器中的输出语句中的
拦截器1
换成拦截器2
,类名换成HandlerInterceptorDemo2
3). 配置文件
<!-- 正常执行的拦截器配置,按照配置的顺序执行 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="handlerInterceptorDemo1" class="com.itheima.myInterceptor.HandlerInterceptorDemo1"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="handlerInterceptorDemo2" class="com.itheima.myInterceptor.HandlerInterceptorDemo2"/>
</mvc:interceptor>
</mvc:interceptors>
4). 测试代码
访问的index.jsp代码和控制器代码同上
【*】测试运行结果...
5). 执行结果
拦截器1:preHandle拦截器拦截了
拦截器2:preHandle拦截器拦截了
控制器中的方法执行了
拦截器2:postHandle方法在DispatcherServlet响应前执行了
拦截器1:postHandle方法在DispatcherServlet响应前执行了
拦截器2:afterCompletion方法在DispatcherServlet响应后执行了
拦截器1:afterCompletion方法在DispatcherServlet响应后执行了
五、中断流程测试
1). 拦截器1代码
同上一个案例的拦截器1的代码
2). 拦截器2代码
public class HandlerInterceptorDemo2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("拦截器2:preHandle拦截器拦截了");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("拦截器2:postHandle方法在DispatcherServlet响应前执行了");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("拦截器2:afterCompletion方法在DispatcherServlet响应后执行了");
}
}
3). 配置文件
同上一个案例的配置文件
4). 测试代码
访问的index.jsp代码和控制器代码同上
【*】测试运行结果...
5). 执行结果
拦截器1:preHandle拦截器拦截了
拦截器2:preHandle拦截器拦截了
拦截器1:afterCompletion方法在DispatcherServlet响应后执行了
六、拦截器的简单案例
验证用户是否登录
1). 实现思路
- 有一个登陆页面,需要写一个controller访问页面。
- 登陆页面有一提交表单的动作。需要在controller中处理。
- 判断用户名密码是否正确。
- 如果正确,向session中写入用户信息。
- 返回登陆成功。
- 拦截用户请求,判断用户是否登陆。
- 如果用户已经登陆。放行
- 如果用户未登陆,跳转到登陆页面
2). 控制器代码
@Controller
@RequestMapping("/user")
public class UserController {
/** 跳转到登陆页面 */
@RequestMapping("/jumplogin")
public String jumpLogin(Model model) throws Exception {
return "login";
}
/** 登陆提交 */
@RequestMapping("/login")
public String login(HttpSession session, String userid, String pwd) throws Exception {
// 向session记录用户身份信息
session.setAttribute("user", userid);
return "success";
}
/** 退出登陆 */
@RequestMapping("logout")
public String logout(HttpSession session) throws Exception {
// session 过期
session.invalidate();
return "login";
}
}
3). 拦截器代码
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
// 如果是登陆页面则放行
System.out.println("uri: " + request.getRequestURI());
if (request.getRequestURI().contains("login")) {
return true;
}
HttpSession session = request.getSession();
// 如果用户已登陆也放行
if(session.getAttribute("user") != null) {
return true;
}
// 用户没有登陆跳转到登陆页面
request.getRequestDispatcher("/WEB-INF/pages/login.jsp").forward(request, response);
return false;
}
}
4). springMVC.xml配置
<!-- 登陆案例的拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="loginInterceptor" class="com.itheima.myInterceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
5). 登陆页面login.jsp
<form action="${pageContext.request.contextPath}/user/login">
用户名:<input type="text" name="userid"> <br>
密码: <input type="password" name="pwd"> <br>
<input type="submit" value="提交">
</form>
6). 测试访问代码
- index.jsp
<%-- 拦截器的提交表单登陆测试 --%>
<a href="${pageContext.request.contextPath}/hello/hello">其他页面访问</a>
- 控制器示例
@Controller
@RequestMapping(path = "/hello")
public class HelloController {
@RequestMapping(path = "/hello", method = RequestMethod.GET)
public String loginOne() {
System.out.println("hello");
return "success";
}
}