Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。
 SpringMVC 中的Interceptor 拦截请求是通过HandlerInterceptor 来实现的。
在SpringMVC 中定义一个Interceptor 主要有两种方式.
- 第一种方式是要定义的Interceptor类要实现了Spring 的HandlerInterceptor 接口,或者是这个类继承实现了HandlerInterceptor 接口的类,如Spring已经提供的实现了HandlerInterceptor 接口的抽象类HandlerInterceptorAdapter;
- 第二种方式是实现Spring的WebRequestInterceptor接口,或者是继承实现了WebRequestInterceptor的类。
一.实现HandlerInterceptor接口
HandlerInterceptor 接口中定义了三个方法,我们就是通过这三个方法来对用户的请求进行拦截处理的。流程:

注意postHandle方法的执行顺序与定义顺序正好相反。
接口实现如下:
| 1 | public class SpringMVCInterceptor implements HandlerInterceptor { | 
- preHandle:预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器(如我们上一章的Controller实现);
 返回值:true表示继续流程(如调用下一个拦截器或处理器);
 false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
- postHandle:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
- afterCompletion:整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中preHandle返回true的拦截器的afterCompletion。在实际应用中,一般都是通过实现HandlerInterceptor接口或者继承HandlerInterceptorAdapter抽象类,复写preHandle()、postHandle()和afterCompletion()这 3 个方法来对用户的请求进行拦截处理的。 
二.WebRequestInterceptor 接口
在WebRequestInterceptor接口中也定义了 3 个方法,同HandlerInterceptor接口完全相同,也是通过复写这 3 个方法来用户的请求进行拦截处理的。而且这 3 个方法都传递了同一个参数 WebRequest,这个 WebRequest 是 Spring 中定义的一个接口,它里面的方法定义跟 HttpServletRequest 类似,在WebRequestInterceptor中对 WebRequest 进行的所有操作都将同步到 HttpServletRequest 中,然后在当前请求中依次传递。
在 Spring 框架之中,还提供了一个和WebRequestInterceptor接口长的很像的抽象类,那就是:WebRequestInterceptorAdapter,其实现了AsyncHandlerInterceptor接口,并在内部调用了WebRequestInterceptor接口。
接下来,咱们主要讲一下WebRequestInterceptor接口的 3 个函数:
- preHandle(WebRequest request)方法,该方法在请求处理之前进行调用,即在 Controller 中的方法调用之前被调用。这个方法跟 HandlerInterceptor 中的 preHandle 不同,主要区别在于该方法的返回值是void 类型的,也就是没有返回值,因此我们主要用它来进行资源的准备工作。在这里,进一步说说 setAttribute 方法的第三个参数 scope ,该参数是一个Integer 类型的。在 WebRequest 的父层接口 RequestAttributes 中对它定义了三个常量,分别为:
- SCOPE_REQUEST ,它的值是 0,表示只有在 request 中可以访问。
- SCOPE_SESSION,它的值是1,如果环境允许的话,它表示的是一个局部的隔离的 session,否则就代表普通的 session,并且在该 session 范围内可以访问。
- SCOPE_GLOBAL_SESSION,它的值是 2,如果环境允许的话,它表示的是一个全局共享的 session,否则就代表普通的 session,并且在该 session 范围内可以访问。
- postHandle(WebRequest request, ModelMap model)方法,该方法在请求处理之后,也就是在 Controller 中的方法调用之后被调用,但是会在视图返回被渲染之前被调用,所以可以在这个方法里面通过改变数据模型 ModelMap 来改变数据的展示。该方法有两个参数,WebRequest 对象是用于传递整个请求数据的,比如在 preHandle 中准备的数据都可以通过 WebRequest 来传递和访问;ModelMap 就是 Controller 处理之后返回的 Model 对象,咱们可以通过改变它的属性来改变返回的 Model 模型。
- afterCompletion(WebRequest request, Exception ex)方法,该方法会在整个请求处理完成,也就是在视图返回并被渲染之后执行。因此可以在该方法中进行资源的释放操作。而 WebRequest 参数就可以把咱们在 preHandle 中准备的资源传递到这里进行释放。Exception 参数表示的是当前请求的异常对象,如果在 Controller 中抛出的异常已经被 Spring 的异常处理器给处理了的话,那么这个异常对象就是是 null.1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21import org.springframework.ui.ModelMap; 
 import org.springframework.web.context.request.WebRequest;
 import org.springframework.web.context.request.WebRequestInterceptor;
 public class WrongCodeInterceptor implements WebRequestInterceptor {
 
 public void preHandle(WebRequest request) throws Exception {
 System.out.println("WrongCodeInterceptor, preHandle......");
 }
 
 public void postHandle(WebRequest request, ModelMap model) throws Exception {
 System.out.println("WrongCodeInterceptor, postHandle......");
 }
 
 public void afterCompletion(WebRequest request, Exception ex) throws Exception {
 System.out.println("WrongCodeInterceptor, afterCompletion......");
 }
 }
拦截器的配置
| 1 | <mvc:interceptors> | 
实例1:SpringMVC拦截器实现登录认证
控制器
/** 
 * 登录认证的控制器 
 */  
@Controller  
public class LoginControl {  
  
    /** 
     * 登录 
     * @param session 
     *          HttpSession 
     * @param username 
     *          用户名 
     * @param password 
     *          密码 
     * @return 
     */  
    @RequestMapping(value="/login")  
    public String login(HttpSession session,String username,String password) throws Exception{        
        //在Session里保存信息  
        session.setAttribute("username", username);  
        //重定向  
        return "redirect:hello.action";   
    }  
      
    /** 
     * 退出系统 
     * @param session 
     *          Session 
     * @return 
     * @throws Exception 
     */  
    @RequestMapping(value="/logout")  
    public String logout(HttpSession session) throws Exception{  
        //清除Session  
        session.invalidate();  
          
        return "redirect:hello.action";  
    }       
} 
拦截器:
/** 
 * 登录认证的拦截器 
 */  
public class LoginInterceptor implements HandlerInterceptor{  
  
    /** 
     * Handler执行完成之后调用这个方法 
     */  
    public void afterCompletion(HttpServletRequest request,  
            HttpServletResponse response, Object handler, Exception exc)  
            throws Exception {  
          
    }  
  
    /** 
     * Handler执行之后,ModelAndView返回之前调用这个方法 
     */  
    public void postHandle(HttpServletRequest request, HttpServletResponse response,  
            Object handler, ModelAndView modelAndView) throws Exception {  
    }  
  
    /** 
     * Handler执行之前调用这个方法 
     */  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,  
            Object handler) throws Exception {  
        //获取请求的URL  
        String url = request.getRequestURI();  
        //URL:login.jsp是公开的;这个demo是除了login.jsp是可以公开访问的,其它的URL都进行拦截控制  
        if(url.indexOf("login.action")>=0){  
            return true;  
        }  
        //获取Session  
        HttpSession session = request.getSession();  
        String username = (String)session.getAttribute("username");  
          
        if(username != null){  
            return true;  
        }  
        //不符合条件的,跳转到登录界面  
        request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);  
          
        return false;  
    }    
}```
在spring的配置文件中配置这个拦截器
```xml
<!-- 拦截器 -->  
        <mvc:interceptors>  
            <!-- 多个拦截器,顺序执行 -->  
            <mvc:interceptor>  
                <mvc:mapping path="/**"/>  
                <bean class="com.mvc.interceptor.LoginInterceptor"></bean>  
            </mvc:interceptor>  
        </mvc:interceptors>
此外,也可在postHandle和afterCompletion中定义拦截逻辑,其中postHandle处理尚未渲染的ModelAndView数据,afterCompletion中处理后置的一些验证等操作.
实例2 性能监控
如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进,一般的反向代理服务器如apache都具有这个功能,但此处我们演示一下使用拦截器怎么实现。
实现分析:
1、在进入处理器之前记录开始时间,即在拦截器的preHandle记录开始时间;
2、在结束请求处理之后记录结束时间,即在拦截器的afterCompletion记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。
问题:
我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即线程不安全,那我们应该怎么记录时间呢?
解决方案是使用ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个ThreadLocal,A线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal)。
public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter {  
    private NamedThreadLocal<Long>  startTimeThreadLocal =   
new NamedThreadLocal<Long>("StopWatch-StartTime");  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,   
Object handler) throws Exception {  
        long beginTime = System.currentTimeMillis();//1、开始时间  
        startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)  
        return true;//继续流程  
    }  
      
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,   
Object handler, Exception ex) throws Exception {  
        long endTime = System.currentTimeMillis();//2、结束时间  
        long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)  
        long consumeTime = endTime - beginTime;//3、消耗的时间  
        if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求  
            //TODO 记录到日志文件  
            System.out.println(  
String.format("%s consume %d millis", request.getRequestURI(), consumeTime));  
        }          
    }  
}
NamedThreadLocal:Spring提供的一个命名的ThreadLocal实现。
在测试时需要把stopWatchHandlerInterceptor放在拦截器链的第一个,这样得到的时间才是比较准确的。