Spring MVC 拦截器

1. 前言

说起 Spring MVC 框架的拦截器,就不得不提起原生 Servlet 中的过滤器,两者的功能性质是一样的,但底层实现机制会有差异性。拦截器用到了代理设计模式,是 Spring AOP 的具体实际应用。过滤器使用的是函数回调机制。

本节课程将和大家一起学习 Spring MVC 中的拦截器。通过本节课程,你将了解到拦截器的工作模式及基础原理。重点是要掌握拦截器的实现过程。

2. 拦截器的概念

拦截器是什么?

拦截器就是一个功能模块,工作模式决定了其与众不同:

  • 非侵入式: Spring MVC 的拦截器是 AOP 编程思想的实际应用,可以在不影响服务对象代码结构的条件下提供附加功能;
  • 动态装配、拆卸: 需要时就装配,不需要时可拆卸。与被服务的对象之间具有极低的耦合度。

2.1 拦截器的工作位置

Spring MVC 中的拦截器和 Servlet 中的过滤器的生命周期、以及服务的目标有差异性。过滤器的生命周期由服务器维护,当请求包进入服务器或响应包即将离开服务器时,过滤器将起作用。

过滤器是在 DispatcherServlet 之前或之后工作,拦截器是请求经过 DispatcherServlet 后进行拦截。

图片描述

拦截器属于 Spring MVC 组件,生命周期由 Spring 上下文容器对象维护。从细节上讲,拦截器可以在用户控制器之前、之后或视图渲染完成之后行使拦截工作。

图片描述

2.2 拦截器应用场景

拦截器可以解决很多实际问题:

  • 日志记录: 记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等;
  • 权限检查: 如登录检测,进入处理器前检测用户是否登录,如果没有直接返回到登录页面;
  • 性能监控: 通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间;
  • 通用行为: 读取 cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 LocaleTheme 信息等只要是多个处理器都需要的即可使用拦截器实现
  • OpenSessionInView:Hibernate,在进入处理器打开 Session,在完成后关闭 Session

除以上应用场景之外,你可以根据自己的业务需要灵活选择。

3. 自定义拦截器

Spring MVC 内置有很多拦截器。这些拦截器提供的功能,基本上能够满足开发者完成常规开发。但是,需求总是瞬息变化的,开发者可以根据自己的业务需求自定义拦截器。

3.1 拦截器接口规范

自定义拦截器之前,首先要了解 Spring MVC 提供的拦截器接口,自定义拦截器必须遵循此接口规范。

public interface HandlerInterceptor {
	/**
	 * 用户控制器之前拦截,实现用户控制器数据的预处理工作,第三个参数为响应的用户控制器
	 */
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return true;
	}
	/**
	 *对用户控制器处理后的数据再进一步处理
	 */
	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}

	/**
	 * 视图解析器对 View 渲染完成后对最后结果进行处理
	 */
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}
}

Spring MVC 提供有拦截器适配器,适配器对拦截器接口做了简单封装。

public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor{
	//……
}

3.2 自定义流程

开发者可以通过实现接口或继承适配器这 2 种方式开发自己的拦截器。如自定义一个拦截器,监控控制器处理时间。

  1. 自定义拦截器类;
public class MyInterceptor extends HandlerInterceptorAdapter {
   	private long startTimer;
	private long endTimer;
   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   	//调用控制器之前的时间
   	startTimer = System.currentTimeMillis();
	 return true;
   }
   
   @Override
   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,   ModelAndView modelAndView) throws Exception {
        //控制器处理完成后的时间
     	endTimer = System.currentTimeMillis();
		System.out.println(endTimer-startTimer);
   }

   @Override
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
      //输出页面渲染完成后的时间
      System.out.println("完成拦截器工作");
   }
}
  1. 通过拦截器配置告诉 Spring 它的存在。配置方式有 2 种:

注解方式: 打开 WebConfig 文件,添加如下代码。

public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
	MyInterceptor myInterceptor = new MyInterceptor();
	registry.addInterceptor(myInterceptor).addPathPatterns("/student/*");
}

Tips: 此拦截器仅对 /student/* 请求地址有效。

xml 方式:

<mvc:interceptors>
     <mvc:interceptor>
         <mvc:mapping path="需要拦截的路径"/>
        <bean class="自定义interceptor全路径名">
 </mvc:interceptor>
</mvc:interceptors>

Tips:本课程以注解为主,XML 在这里只做简要介绍。

  1. 编写测试控制器;
@RequestMapping("/student")
public class StudentAction {
	@RequestMapping("/test")
	public String interceptor() {
		return "index";
	}
}
  1. 测试拦截是否能正常工作。
    发布项目,启动 tomcat 服务器,在浏览器中输入 http://localhost:8888/sm-demo/student/test ,查看控制台上的输出。
    图片描述

3.3 拦截器链

所谓拦截器链,指多个拦截器一起协作工作,拦截器一起工作时,请注意拦截器中的各个方法之间的调用顺序。

图片描述

前面拦截器的 preHandle 方法的返回值会影响后面的拦截器和控制器是否正常工作:

  • 如果返回 true 表示继续流程,可继续调用下一个拦截器或进入控制器;
  • 如果返回 false 表示流程中断,如登录身份检查失败。不会继续调用其他的拦截器或处理器。

Tips : 如果第一个拦截器的 preHandle 方法返回 true,则会进入第二个拦截器。如果第二个拦截器的 preHandle 方法 返回 false,则直接进入第一个拦截器的 afterCompletion 方法。

4. 小结

本章节和大家讲解了 Spring MVC 的拦截器,需要大家掌握拦截的工作原理以及实现自定义拦截器。特别注意的是要理解多个拦截器在一起协作工作时方法之间是如何调用的。