Spring 拦截器
目录
任何优秀的 MVC 框架,都会提供一些通用的操作,如:请求数据的封装、类型转换、数据校验、解析上传的文件、防止表单的多次提交等。早期的 MVC 框架将这些操作都写死在核心控制器中,而这些常用的操作又不是所有的请求都需要实现的,这就导致了框架的灵活性不足,可扩展性降低。拦截器作为 Spring MVC 中强大的控件,它可以在请求进入处理器之前做一些操作,或者在处理器完成后进行操作,甚至是在渲染视图后进行操作
¶概述
SpringMVC 提供了 Interceptor 拦截器机制,类似于 Servlet 中的 Filter 过滤器,用于拦截用户的请求并做出相应的处理。比如通过拦截器来进行用户权限验证,或者用来判断用户是否已经登录。Spring MVC 拦截器是可插拔式的设计,需要某一功能拦截器,只需在配置文件中应用该拦截器即可;如果不需要这个功能拦截器,只需在配置文件中取消应用该拦截器
¶实现自定义的拦截器
¶通过 HandlerInterceptor
接口
HandlerInterceptor 位于 org.springframework.web.servlet
的包中,定义了三个方法,若要实现该接口,就要实现其三个方法:
preHandle()
方法:该方法在执行控制器方法之前执行。返回值为 Boolean 类型,如果返回 false,表示拦截请求,不再向下执行,如果返回 true,表示放行,程序继续向下执行(如果后面没有其他 Interceptor,就会执行 controller 方法)。所以此方法可对请求进行判断,决定程序是否继续执行,或者进行一些初始化操作及对请求进行预处理postHandle()
方法:该方法在执行控制器方法调用之后,且在返回 ModelAndView 之前执行。由于该方法会在 DispatcherServlet 进行返回视图渲染之前被调用,所以此方法多被用于处理返回的视图,可通过此方法对请求域中的模型和视图做进一步的修改afterCompletion()
方法:该方法在执行完控制器之后执行,由于是在 Controller 方法执行完毕后执行该方法,所以该方法适合进行一些资源清理,记录日志信息等处理操作
实现了 HandlerInterceptor 接口之后,需要在 Spring 的类加载配置文件中配置拦截器实现类,才能使拦截器起到拦截的效果,加载配置有两种方式:
这里为 BeanNameUrlHandlerMapping 处理器配置了一个 interceptors 拦截器链,该拦截器链包含了 myInterceptor1 和 myInterceptor2 两个拦截器,具体实现分别对应下面 id 为 myInterceptor1 和 myInterceptor2 的 bean 配置
在上面的配置中,可在 mvc:interceptors 标签下配置多个拦截器其子元素 bean 定义的是全局拦截器,它会拦截所有的请求;而 mvc:interceptor 元素中定义的是指定元素的拦截器,它会对指定路径下的请求生效,其子元素必须按照mvc:mapping --> mvc:exclude-mapping --> bean
的顺序,否则文件会报错
¶通过 WebRequestInterceptor
接口
WebRequestInterceptor 中也定义了三个方法,也是通过这三个方法来实现拦截的。这三个方法都传递了同一个参数 WebRequest, WebRequest 是 Spring 定义的一个接口,它里面的方法定义都基本跟 HttpServletRequest 一样,在 WebRequestInterceptor 中对 WebRequest 进行的所有操作都将同步到 HttpServletRequest 中,然后在当前请求中一直传递。三个方法如下:
preHandle(WebRequest request)
:WebRequestInterceptor 的该方法返回值为 void,不是 boolean。所以该方法不能用于请求阻断,一般用于资源准备。postHandle(WebRequest request, ModelMap model)
:preHandle 中准备的数据都可以通过参数 WebRequest 访问。ModelMap 是 Controller 处理之后返回的 Model 对象,可以通过改变它的属性来改变 Model 对象模型,达到改变视图渲染效果的目的。afterCompletion(WebRequest request, Exception ex)
:Exception 参数表示的是当前请求的异常对象,如果 Controller 抛出的异常已经被处理过,则 Exception 对象为 null
¶拦截器执行流程
通过上文的步骤能够定义并使用拦截器,那么自定义拦截器的执行流程是什么?下文将进行详细的阐述
¶单个拦截器的执行流程
运行程序时,拦截器的执行是有一定顺序的,该顺序与配置文件中所定义的拦截的顺序相关。如果程序中只定义了一个拦截器,则该单个拦截器在程序中的执行流程如图所示
程序首先执行拦截器类中的 preHandle() 方法,如果该方法返回值是 true,则程序会继续向下执行处理器中的方法,否则不再向下执行;在业务控制器类 Controller 处理完请求后,会执行 postHandle()方法,而后会通过 DispatcherServlet 向客户端返回相应;在 DispatcherServlet 处理完请求后,才会执行 afterCompletion()方法
¶多个拦截器的执行流程
在一个 Web 工程中,甚至在一个 HandlerMapping 处理器适配器中都可以配置多个拦截器,每个拦截器都按照提前配置好的顺序执行。它们内部的执行规律并不像多个普通 Java 类一样,它们的设计模式是基于「 责任链 」的模式
下面通过图例来描述多个拦截器的执行流程,假设有两个拦截器 MyInterceptor1 和 MyInterceptor2,将 MyInterceptor1 配置在前,如图所示
¶拦截器与过滤器的关系
过滤器和拦截器都能够做到对相关的请求进行拦截,那么两者之间到底有什么不同?
¶过滤器(Filter)
过滤器的配置比较简单,直接实现Filter
接口即可,也可以通过@WebFilter
注解实现对特定URL
拦截,看到Filter
接口中定义了三个方法
init()
:该方法在容器启动初始化过滤器时被调用,它在Filter
的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用doFilter()
:容器中的每一次请求都会调用该方法,FilterChain
用来调用下一个过滤器Filter
destroy()
:当容器销毁过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器Filter
的整个生命周期也只会被调用一次
1 |
|
¶拦截器(Interceptor)
拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor
, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行,在 Spring Boot 中,将自定义好的拦截器处理类进行注册,并通过addPathPatterns
、excludePathPatterns
等属性设置需要拦截或需要排除的 URL
1 |
|
¶过滤器与拦截器的异同
过滤器 和 拦截器 均体现了AOP
的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的
¶实现原理不同
过滤器和拦截器 底层实现方式大不相同,过滤器
是基于函数回调的,拦截器
则是基于 Java 的反射机制(动态代理)实现的
在我们自定义的过滤器中都会实现一个 doFilter()
方法,这个方法有一个FilterChain
参数,而实际上它是一个回调接口。ApplicationFilterChain
是它的实现类, 这个实现类内部也有一个 doFilter()
方法就是回调方法
1 | public interface FilterChain { |
ApplicationFilterChain
里面能拿到我们自定义的xxxFilter
类,在其内部回调方法doFilter()
里调用各个自定义xxxFilter
过滤器,并执行 doFilter()
方法
1 | public final class ApplicationFilterChain implements FilterChain { |
而每个xxxFilter
会先执行自身的 doFilter()
过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse)
,也就是回调ApplicationFilterChain
的doFilter()
方法,以此循环执行实现函数回调
1 |
|
¶使用范围不同
过滤器实现的是 javax.servlet.Filter
接口,而这个接口是在Servlet
规范中定义的,也就是说过滤器Filter
的使用要依赖于Tomcat
等容器,导致它只能在web
程序中使用
拦截器(Interceptor
) 它是一个Spring
组件,并由Spring
容器管理,并不依赖Tomcat
等容器,是可以单独使用的。不仅能应用在web
程序中,也可以用于Application
、Swing
等程序中
¶触发时机不同
过滤器
和 拦截器
的触发时机也不同
过滤器Filter
是在请求进入容器后,但在进入servlet
之前进行预处理,请求结束是在servlet
处理完以后
拦截器 Interceptor
是在请求进入servlet
后,在进入Controller
之前进行预处理的,Controller
中渲染了对应的视图之后请求结束
¶拦截的请求范围不同
请求正确访问的拦截顺序是:
1 | Filter 处理中 --> Interceptor 前置 -> Controller Method -> Interceptor 处理中 -> Interceptor 处理后 -> Filter 处理中 |
过滤器Filter
执行了两次,拦截器Interceptor
只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller
中请求或访问static
目录下的资源请求起作用
¶控制执行顺序不同
实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序
过滤器用@Order
注解控制执行顺序,通过@Order
控制过滤器的级别,值越小级别越高越先执行
1 |
|
拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order
手动设置控制,值越小越先执行
1 |
|
看到输出结果发现,先声明的拦截器 preHandle()
方法先执行,而postHandle()
方法反而会后执行
¶总结
过滤器
、 拦截器
和 AOP
三者在选择通常要先明确 访问控制功能
的粒度,如果访问控制功能要精确到每个请求,那么要使用 AOP
,AOP
可以配置每个 Controller
的访问权限。而 拦截器
和 过滤器
的粒度相对来说会粗一些,控制 HttpRequest
、HttpResponse
的访问。另外还有一点就是 过滤器
不能够使用 Spring
容器资源,只能在 Servlet
容器(e.g. tomcat
)启动时调用一次,而 拦截器
是 Spring
提供的组件,由 Spring
来管理,因此它能使用 Spring
里的任何资源、对象,例如 Service
对象、数据源、事务管理等等,通过 IoC
注入到 拦截器
中即可。相比较而言,拦截器
要更灵活一些。