※. 处理器拦截器
SpringMVC的处理器拦截器类似于Servlet 开发中的过滤器Filter,
用于对处理器进行预处理和后处理。
1) 常见应用场景
1、日志记录
2、权限检查
3、性能监控
4、通用行为 例如读取用户cookie
5、OpenSessionInView 例如在Hibernate中,在进入处理器前打开Session,在完成后关闭Session。
等
2) 拦截器接口
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception;
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception;
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;
}
preHandle方法(相当于前置通知)
预处理回调方法,实现处理器的预处理,第三个参数为的处理器(本次请求要访问的那个Controller)
返回值:true表示继续流程(如调用下一个拦截器或处理器)
false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应
postHandle方法
后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
afterCompletion方法
整个请求处理完毕回调方法,即在视图渲染完毕时回调
1.控制器
package com.briup.web.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class FiveController
implements Controller{
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv=
new ModelAndView();
System.out.println("FiveController.....");
mv.setViewName("hello");
return mv;
}
}
2.构建拦截器
package com.briup.web.Interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class defineInterceptor
extends HandlerInterceptorAdapter{
/*执行处理器之前的操作,
* 相当于前置通知
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("before....defineInterceptor1");
//true就是放行,原来执行那个
//controller接着执行那个controller
//false终止原来的请求
return true;
}
/*
* Controller控制器执行之后,
* 视图渲染之前做的操作
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle....defineInterceptor1");
}
/*
* 在视图解析器渲染视图之后的操作
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("afterCompletion....defineInterceptor1");
}
}
3.spring.xml文件配置
<bean name="myInterceptor"
class="com.briup.web.Interceptor.defineInterceptor"></bean>
<!-- 一旦自定义了拦截器,
映射器就不能省略 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<!-- 配置拦截器
,在映射器中配置的拦截器,对
所有的controller处理器生效
interceptors List<Object>
-->
<property name="interceptors">
<array>
<ref bean="myInterceptor"/>
</array>
</property>
</bean>
<!-- 适配器,默认,控制器实现了接口controller
里面有重写方法的时候配置SimpleControllerHandlerAdapter -->
<bean class="com.briup.web.adapter.DefineAdapter"></bean>
<bean name="/five"
class="com.briup.web.controller.FiveController"></bean>
3) 拦截器适配器
有时候我们可能只需要实现三个回调方法中的某一个,如果实现HandlerInterceptor 接口的话,
三个方法必须实现,不管你需不需要,此时spring 提供了一个HandlerInterceptorAdapter 适配器(适配器模式),允许我们只实现需要的回调方法。
在HandlerInterceptorAdapter中,对HandlerInterceptor 接口中的三个方法都进行了空实现,其中preHandle方法的返回值,默认是true
拦截器:
package com.briup.web.Interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class defineInterceptor1
extends HandlerInterceptorAdapter{
public defineInterceptor1() {
System.out.println("defineInterceptor1 create");
}
/*
* 执行controller|handler
* 中的方法之前做的操作
* true 放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("before...defineInterceptor2");
return true;
}
/*
* 控制器controller中方法执行之后
* 视图渲染之前做的操作
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle.....defineInterceptor2");
}
/*
* 视图渲染之后做的操作
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("afterCompletion...defineInterceptor2");
}
}
spring.xml映射文件
<bean name="myInterceptor"
class="com.briup.web.Interceptor.defineInterceptor"></bean>
<!-- 一旦自定义了拦截器,
映射器就不能省略 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<!-- 配置拦截器
,在映射器中配置的拦截器,对
所有的controller处理器生效
interceptors List<Object>
配置多个拦截器的时候
顺序在先的拦截器先执行前置方法,
当执行完控制器
中controller方法之后先执行的拦截器后执行-->
<property name="interceptors">
<array>
<bean class="com.briup.web.Interceptor.defineInterceptor1"></bean>
<ref bean="myInterceptor"/>
</array>
</property>
</bean>
4) 测试一个拦截器
拦截器代码:
public class MyInterceptor1 extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("MyInterceptor1 preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor1 postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("MyInterceptor1 afterCompletion");
}
}
配置文件:
<bean name="handlerInterceptor1" class="com.briup.web.interceptor.MyInterceptor1"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
</list>
</property>
</bean>
访问一个测试的Controller查看结果:
MyInterceptor1 preHandle
TestController执行
MyInterceptor1 postHandle
MyInterceptor1 afterCompletion
5) 测试俩个拦截器
俩个拦截器的代码和上面类似,只是每个输出的内容不同。配置多个拦截器的时候
顺序在先的拦截器先执行前置方法,当执行完控制器中controller方法之后先执行的拦截器后执行。
配置文件:
<bean name="handlerInterceptor1" class="com.briup.web.interceptor.MyInterceptor1"/>
<bean name="handlerInterceptor2" class="com.briup.web.interceptor.MyInterceptor1"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
<ref bean="handlerInterceptor2"/>
</list>
</property>
</bean>
访问一个测试的Controller查看结果:
MyInterceptor1 preHandle
MyInterceptor2 preHandle
TestController执行
MyInterceptor2 postHandle
MyInterceptor1 postHandle
MyInterceptor2 afterCompletion
MyInterceptor1 afterCompletion
注意:<list>标签中引用拦截器的顺序会影响结果输出的顺序
6) 如果Controller等采用的注解配置,那么拦截器需要mvc标签进行配置
注意:每个<mvc:interceptor>只能配置一个拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<ref bean="handlerInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
例如1: 注意/*和/**的区别
<mvc:interceptors>
<!-- 下面所有的mvc映射路径都会被这个拦截器拦截 -->
<bean class="com.briup.web.interceptor.MyInterceptor1" />
<mvc:interceptor>
<mapping path="/**"/>
<exclude-mapping path="/admin/**"/>
<bean class="com.briup.web.interceptor.MyInterceptor2" />
</mvc:interceptor>
<mvc:interceptor>
<mapping path="/secure/*"/>
<bean class="com.briup.web.interceptor.MyInterceptor3" />
</mvc:interceptor>
</mvc:interceptors>
例:spring配置文件:
<!-- 给指定的请求设置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- path对资源请求的限定 -->
<mvc:mapping path="/four"/>
<!-- 不加入拦截器 -->
<mvc:exclude-mapping path="/hello.do"/>
<!-- 指的是拦截器 -->
<bean class="com.briup.web.Interceptor.defineInterceptor1"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/second"/>
<ref bean="myInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<!-- /* /后面直接是资源名称(后面不能在出现斜杠)
/first /first/test
http://localhost:8888/jd1812_MVC/first/test
/** 资源名称中允许出现多个斜杠 -->
<mvc:mapping path="/**"/>
<bean class="com.briup.web.Interceptor.defineInterceptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
7) 拦截器是单例
因此不管多少用户请求多少次都只有一个拦截器实现,即线程不安全。
所以在必要时可以在拦截器中使用ThreadLocal,它是和线程绑定,一个线程一个ThreadLocal,
A 线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal。
8) 记录执行Controller所用时间
public class TimeInterceptor extends HandlerInterceptorAdapter{
//拦截器是单例,不是线程安全的,所以这里使用ThreadLocal
private ThreadLocal<Long> local = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long start = System.currentTimeMillis();
local.set(start);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
long end = System.currentTimeMillis();
System.out.println("共耗时:"+(end-local.get()));
}
}
例:
拦截器:
package com.briup.web.Interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/*
* 拦截器单列,线程不安全
* 多个请求同时访问相同控制器中的方法
* 的时候,该方法只有一个拦截器
*/
public class TimeInterceptor
extends HandlerInterceptorAdapter{
/*
* ThreadLocal 单多线程环境
* 操作相同的变量的时候
* ThreadLocal和每一个线程绑定的
* 变量,能保证线程的安全
* 注意:ThreadLocal内部只能存储
* 一个值
*/
private ThreadLocal<Long> timer=
new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long start_date=
System.currentTimeMillis();
timer.set(start_date);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
long end_date=
System.currentTimeMillis();
long start_date=timer.get();
System.out.println("总时间:"
+(end_date-start_date));
}
}
spring配置:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hello.do"/>
<bean class="com.briup.web.Interceptor.TimeInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
拓展:利用ThreadLocal对象达到不同层传参。
9) 登录检查
public class LoginInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//请求到登录页面放行
if(request.getServletPath().startsWith("/login")) {
return true;
}
//如果用户已经登录放行
if(request.getSession().getAttribute("username") != null) {
return true;
}
//重定向到登录页面
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}
注意:推荐能使用servlet规范中的过滤器Filter实现的功能就用Filter实现,因为HandlerInteceptor只有在SpringWebMVC环境下才能使用,因此Filter是最通用的、最先应该使用的。
例:
访问登录页面:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<base href="<%=basePath %>">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
</head>
<body>
登陆页面
</body>
</html>
控制器Controller:
package com.briup.web.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class LoginController
implements Controller{
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv=
new ModelAndView();
mv.setViewName("login");
return mv;
}
}
拦截器:
package com.briup.web.Interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class LoginInterceptor
extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//通过request获取请求的资源名称
// System.out.println(
// request.getServletPath()+"***");
//通过request获取项目的名字
// System.out.println(
// request.getContextPath()+"&&&");
if(request.getServletPath().startsWith("/login")){
return true;
}
if(request.getSession().getAttribute("user")!=null){
return true;
}
/*
* 直接进入登陆页面
*/
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp")
.forward(request, response);
// /jd1812_MVC/login
// response.sendRedirect(
// request.getContextPath()+"/login");
return false;
}
}
spring配置:
<mvc:interceptor>
<!-- /* /后面直接是资源名称(后面不能在出现斜杠)
/first /first/test
http://localhost:8888/jd1812_MVC/first/test
/** 资源名称中允许出现多个斜杠 -->
<mvc:mapping path="/**"/>
<bean class="com.briup.web.Interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
<bean name="/login" class="com.briup.web.controller.LoginController"></bean>