昨天新接了一个须要,“拦截 XXX,而后 OOO”,好吧,说白了就是要用拦截器干点事(实现一个具体的功能)。以前,也在网络上搜了不少关于Interceptor
的文章,但感受内容都大同小异,并且知识点零零散散,不太方便阅读。所以,正好借此机会,整理一篇关于拦截器的文章,在此分享给你们,以供你们参考阅读。java
Java 里的拦截器是动态拦截 action 调用的对象。它提供了一种机制可使开发者能够定义在一个 action 执行的先后执行的代码,也能够在一个 action 执行前阻止其执行,同时也提供了一种能够提取 action 中可重用部分的方式。在AOP(Aspect-Oriented Programming,面向切面编程)中拦截器用于在某个方法或字段被访问以前进行拦截,而后在以前或以后加入某些操做。web
拦截器 Interceptor 的拦截功能是基于 Java 的动态代理来实现的,具体能够参考博文“ 用 Java 实现拦截器 Interceptor 的拦截功能 ”,也能够经过阅读 Spring 源代码来了解更为权威的实现细节。spring
在 Spring 框架之中,我们要想实现拦截器的功能,主要经过两种途径,第一种是实现HandlerInterceptor
接口,第二种是实现WebRequestInterceptor
接口。接下来,我们分别详细的介绍二者的实现方法。apache
在HandlerInterceptor
接口中,定义了 3 个方法,分别为preHandle()
、postHandle()
和afterCompletion()
,我们就是经过复写这 3 个方法来对用户的请求进行拦截处理的。所以,我们能够经过直接实现HandlerInterceptor
接口来实现拦截器的功能。不过在 Spring 框架之中,其还提供了另一个接口和一个抽象类,实现了对HandlerInterceptor
接口的功能扩展,分别为:AsyncHandlerInterceptor
和HandlerInterceptorAdapter
.编程
对于AsyncHandlerInterceptor
接口,其在继承HandlerInterceptor
接口的同时,又声明了一个新的方法afterConcurrentHandlingStarted()
;而HandlerInterceptorAdapter
抽象类,则是更进一步,在其继承AsyncHandlerInterceptor
接口的同时,又复写了preHandle
方法。所以,AsyncHandlerInterceptor
更像是一个过渡的接口。spring-mvc
在实际应用中,我们通常都是经过实现HandlerInterceptor
接口或者继承HandlerInterceptorAdapter
抽象类,复写preHandle()
、postHandle()
和afterCompletion()
这 3 个方法来对用户的请求进行拦截处理的。下面,我们就详细介绍这个 3 个方法。网络
preHandle(HttpServletRequest request, HttpServletResponse response, Object handle)
方法,该方法在请求处理以前进行调用。SpringMVC 中的 Interceptor 是链式调用的,在一个应用中或者说是在一个请求中能够同时存在多个 Interceptor 。每一个 Interceptor 的调用会依据它的声明顺序依次执行,并且最早执行的都是 Interceptor 中的 preHandle 方法,因此能够在这个方法中进行一些前置初始化操做或者是对当前请求作一个预处理,也能够在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值 Boolean 类型的,当它返回为 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当返回值为 true 时,就会继续调用下一个 Interceptor 的 preHandle 方法,若是已是最后一个 Interceptor 的时候,就会是调用当前请求的 Controller 中的方法。postHandle(HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView)
方法,经过 preHandle 方法的解释我们知道这个方法包括后面要说到的 afterCompletion 方法都只能在当前所属的 Interceptor 的 preHandle 方法的返回值为 true 的时候,才能被调用。postHandle 方法在当前请求进行处理以后,也就是在 Controller 中的方法调用以后执行,可是它会在 DispatcherServlet 进行视图返回渲染以前被调用,因此我们能够在这个方法中对 Controller 处理以后的 ModelAndView 对象进行操做。postHandle 方法被调用的方向跟 preHandle 是相反的,也就是说,先声明的 Interceptor 的 postHandle 方法反而会后执行。这和 Struts2 里面的 Interceptor 的执行过程有点类型,Struts2 里面的 Interceptor 的执行过程也是链式的,只是在 Struts2 里面须要手动调用 ActionInvocation 的 invoke 方法来触发对下一个 Interceptor 或者是 action 的调用,而后每个 Interceptor 中在 invoke 方法调用以前的内容都是按照声明顺序执行的,而 invoke 方法以后的内容就是反向的。afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)
方法,也是须要当前对应的 Interceptor 的 preHandle 方法的返回值为 true 时才会执行。所以,该方法将在整个请求结束以后,也就是在 DispatcherServlet 渲染了对应的视图以后执行,这个方法的主要做用是用于进行资源清理的工做。接下来,我们在看看以上接口和抽象类的具体代码:session
HandlerInterceptor
接口:mvc
package org.springframework.web.servlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; 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; }
AsyncHandlerInterceptor
接口:app
package org.springframework.web.servlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public interface AsyncHandlerInterceptor extends HandlerInterceptor { void afterConcurrentHandlingStarted( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; }
HandlerInterceptorAdapter
抽象类:
package org.springframework.web.servlet.handler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * Abstract adapter class for the HandlerInterceptor interface, * for simplified implementation of pre-only/post-only interceptors. * * @author Juergen Hoeller * @since 05.12.2003 */ public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { /** * This implementation always returns {@code true}. */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } /** * This implementation is empty. */ public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * This implementation is empty. */ public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } /** * This implementation is empty. */ public void afterConcurrentHandlingStarted( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { } }
如上面的代码所示,其实在HandlerInterceptor
和AsyncHandlerInterceptor
中还有不少的代码注释,只是博主感受太多了,就将其所有删除啦!若是你们对这些注释感兴趣的话,能够自行查看源代码。下面,我们以继承HandlerInterceptorAdapter
抽象类为例进行演示:
package com.hit.interceptor; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author 维C果糖 * @create 2017-03-31 */ public class WrongCodeInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("WrongCodeInterceptor, preHandle......"); return true; } @Override public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("WrongCodeInterceptor, postHandle......"); } @Override public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("WrongCodeInterceptor, afterCompletion......"); } @Override public void afterConcurrentHandlingStarted( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("WrongCodeInterceptor, afterConcurrentHandlingStarted......"); } }
在WebRequestInterceptor
接口中也定义了 3 个方法,同HandlerInterceptor
接口彻底相同,我们也是经过复写这 3 个方法来用户的请求进行拦截处理的。并且这 3 个方法都传递了同一个参数 WebRequest,那么这个 WebRequest 究竟是什么呢?其实这个 WebRequest 是 Spring 中定义的一个接口,它里面的方法定义跟 HttpServletRequest 相似,在WebRequestInterceptor
中对 WebRequest 进行的全部操做都将同步到 HttpServletRequest 中,而后在当前请求中依次传递。
在 Spring 框架之中,还提供了一个和WebRequestInterceptor
接口长的很像的抽象类,那就是:WebRequestInterceptorAdapter
,其实现了AsyncHandlerInterceptor
接口,并在内部调用了WebRequestInterceptor
接口。
接下来,我们主要讲一下WebRequestInterceptor
接口的 3 个函数:
preHandle(WebRequest request)
方法,该方法在请求处理以前进行调用,也就是说,其会在 Controller 中的方法调用以前被调用。这个方法跟 HandlerInterceptor 中的 preHandle 不一样,主要区别在于该方法的返回值是void 类型的,也就是没有返回值,所以咱们主要用它来进行资源的准备工做,好比咱们在使用 Hibernate 的时候,能够在这个方法中准备一个 Hibernate 的Session 对象,而后利用 WebRequest 的 setAttribute(name, value, scope) 把它放到 WebRequest 的属性中。在这里,进一步说说 setAttribute 方法的第三个参数 scope ,该参数是一个Integer 类型的。在 WebRequest 的父层接口 RequestAttributes 中对它定义了三个常量,分别为:
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.接下来,我们在看看以上接口和抽象类的具体代码:
WebRequestInterceptor
接口:
package org.springframework.web.context.request; import org.springframework.ui.ModelMap; public interface WebRequestInterceptor { void preHandle(WebRequest request) throws Exception; void postHandle(WebRequest request, ModelMap model) throws Exception; void afterCompletion(WebRequest request, Exception ex) throws Exception; }
WebRequestInterceptorAdapter
抽象类:
package org.springframework.web.servlet.handler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.util.Assert; import org.springframework.web.context.request.AsyncWebRequestInterceptor; import org.springframework.web.context.request.WebRequestInterceptor; import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * Adapter that implements the Servlet HandlerInterceptor interface * and wraps an underlying WebRequestInterceptor. * * @author Juergen Hoeller * @since 2.0 * @see org.springframework.web.context.request.WebRequestInterceptor * @see org.springframework.web.servlet.HandlerInterceptor */ public class WebRequestHandlerInterceptorAdapter implements AsyncHandlerInterceptor { private final WebRequestInterceptor requestInterceptor; /** * Create a new WebRequestHandlerInterceptorAdapter for the given WebRequestInterceptor. * @param requestInterceptor the WebRequestInterceptor to wrap */ public WebRequestHandlerInterceptorAdapter(WebRequestInterceptor requestInterceptor) { Assert.notNull(requestInterceptor, "WebRequestInterceptor must not be null"); this.requestInterceptor = requestInterceptor; } public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { this.requestInterceptor.preHandle(new DispatcherServletWebRequest(request, response)); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { this.requestInterceptor.postHandle(new DispatcherServletWebRequest(request, response), (modelAndView != null && !modelAndView.wasCleared() ? modelAndView.getModelMap() : null)); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { this.requestInterceptor.afterCompletion(new DispatcherServletWebRequest(request, response), ex); } public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) { if (this.requestInterceptor instanceof AsyncWebRequestInterceptor) { AsyncWebRequestInterceptor asyncInterceptor = (AsyncWebRequestInterceptor) this.requestInterceptor; DispatcherServletWebRequest webRequest = new DispatcherServletWebRequest(request, response); asyncInterceptor.afterConcurrentHandlingStarted(webRequest); } } }
如上面的代码所示,展现了WebRequestInterceptor
接口和WebRequestInterceptorAdapter
抽象类的源码。下面,我们以实现WebRequestInterceptor
接口为例进行演示:
package com.hit.interceptor; import org.springframework.ui.ModelMap; import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequestInterceptor; /** * @author 维C果糖 * @create 2017-03-31 */ public class WrongCodeInterceptor implements WebRequestInterceptor { @Override public void preHandle(WebRequest request) throws Exception { System.out.println("WrongCodeInterceptor, preHandle......"); } @Override public void postHandle(WebRequest request, ModelMap model) throws Exception { System.out.println("WrongCodeInterceptor, postHandle......"); } @Override public void afterCompletion(WebRequest request, Exception ex) throws Exception { System.out.println("WrongCodeInterceptor, afterCompletion......"); } }
除了上面3.2
和3.3
所讲的内容,我们还能够经过继承 Struts2
框架提供的AbstractInterceptor
抽象类来实现拦截的功能。若是我们在深刻一点研究,会发现AbstractInterceptor
实现了Interceptor
接口,而Interceptor
接口又继承了Serializable
接口。
在Interceptor
接口中,提供了 3 个方法供我们使用,分别为init()
、destroy()
和intercept()
,因为AbstractInterceptor
实现了Interceptor
接口,所以我们就能够直接继承AbstractInterceptor
,而后复写方法就能够啦!至于为何继承AbstractInterceptor
而不是直接实现Interceptor
接口,是由于AbstractInterceptor
已经帮我们实现了空的init()
和destroy()
方法,不须要我们本身去复写了,我们直接复写intercept()
方法就能够啦!如今,我们大体了解一下这 3 个方法的做用:
init()
方法,通常用来进行初始化操做;destroy()
方法,通常用来进行释放资源的操做;intercept()
方法,该方法是实现拦截功能的主要方法,我们就在该方法中编写拦截的逻辑。接下来,我们在看看以上接口和抽象类的具体代码:
Interceptor
接口:
package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; import java.io.Serializable; public interface Interceptor extends Serializable { /** * Called to let an interceptor clean up any resources it has allocated. */ void destroy(); /** * Called after an interceptor is created, but before any requests are processed using * {@link #intercept(com.opensymphony.xwork2.ActionInvocation) intercept} , giving * the Interceptor a chance to initialize any needed resources. */ void init(); /** * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the * request by the {@link ActionInvocation} or to short-circuit the processing and just return a String return code. * * @param invocation the action invocation * @return the return code, either returned from {@link ActionInvocation#invoke()}, or from the interceptor itself. * @throws Exception any system-level error, as defined in {@link com.opensymphony.xwork2.Action#execute()}. */ String intercept(ActionInvocation invocation) throws Exception; }
AbstractInterceptor
接口:
package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; /** * Provides default implementations of optional lifecycle methods */ public abstract class AbstractInterceptor implements Interceptor { /** * Does nothing */ public void init() { } /** * Does nothing */ public void destroy() { } /** * Override to handle interception */ public abstract String intercept(ActionInvocation invocation) throws Exception; }
如上面的代码所示,展现了Interceptor
接口和AbstractInterceptor
抽象类的源码。下面,我们以继承AbstractInterceptor
抽象类为例进行演示:
package com.hit.interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; import org.apache.struts2.ServletActionContext; /** * @author 维C果糖 * @create 2017-03-31 */ public class WrongCodeInterceptor extends AbstractInterceptor { /** * 经过拦截功能,验证用户是否登陆 */ public String intercept(ActionInvocation invocation) throws Exception { UserInfo info = (UserInfo) ServletActionContext.getRequest().getSession().getAttribute("user"); if(info != null && !info.getName().equals("") && !info.getPwd().equals("")) { return invocation.invoke(); } return "login"; } }
UserInfo
类文件:
/** * @author 维C果糖 * @create 2017-03-31 */ public class UserInfo { String name; String pwd; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } }
在前面,我们用了很大篇幅的内容讲述了拦截器如何实现,所以,我相信你们实现拦截器已经没有问题啦!接下来,我们在看看,如何在 XML 文件中配置拦截器,使我们的拦截器生效。
在配置拦截器以前,有 4 个名称的概念须要你们先了解一下,分别为:Join Point
、Pointcut
、Advice
和Advisor
.
接下来,给出 XML 配置文件的声明:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
在 XML 文件的头部声明完以后,我们就能够在 Spring 的配置文件中就可使用mvc
标签啦!而在mvc
标签中有一个名为mvc:interceptors
的标签,该标签就是用于声明 Spring 拦截器的。下面,给出一个配置示例:
<mvc:interceptors> <!-- 使用 bean 定义一个 Interceptor,直接定义在 mvc:interceptors 下面的 Interceptor 将拦截全部的请求 --> <bean class="com.hit.interceptor.WrongCodeInterceptor"/> <mvc:interceptor> <mvc:mapping path="/demo/hello.do"/> <!-- 定义在 mvc:interceptor 下面的 Interceptor,表示对特定的请求进行拦截 --> <bean class="com.hit.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>
在 Spring 的XML 配置文件中,我们能够经过mvc:interceptors
标签声明一系列的拦截器,例如:
<mvc:interceptors> <bean class="com.hit.interceptor.ContextInterceptor"/> <bean class="com.hit.interceptor.LoginInterceptor"/> <bean class="com.hit.interceptor.WrongCodeInterceptor"/> </mvc:interceptors>
如上所示,这些拦截器就够成了一个拦截器链,或者称之为拦截器栈。而这些拦截器的执行顺序是按声明的前后顺序执行的,即:先声明的拦截器先执行,后声明的拦截器后执行。在mvc:interceptors
标签下声明interceptor
标签主要有两种方式:
mvc:interceptor
标签进行声明,使用这种方式进行声明的 Interceptor 能够经过mvc:mapping
子标签来定义须要进行拦截的请求路径。此外,因为拦截器是 AOP 编程思想的典型应用,也就意味着我们能够“切”到具体的“面”进行某些操做。例如,
<bean id="WrongCodeInterceptor" class="com.hit.interceptor.WrongCodeInterceptor"> <property name="userName" value="user-module"></property> </bean> <bean id="loginInterceptor" class="com.hit.interceptor.LoginInterceptor"> <property name="excludePackages"> <list> <value>com.hit.user.exception</value> <value>com.hit.order.exception</value> </list> </property> </bean> <aop:config> <aop:advisor advice-ref="WrongCodeInterceptor" pointcut="execution(* com.hit.*.demo..*.*(..)) || execution(* com.hit.*.demo..*.*(..)) " /> <aop:advisor advice-ref="loginInterceptor" pointcut="execution(* com.hit.*.demo..*.*(..))" /> </aop:config>
如上所示,我们实现了切入到“面”进行特定的拦截功能,其中pointcut
表示“切入点”,advisor
表示要注入到pointcut
的代码。你们可能会对pointcut
中的*
符合有所疑惑,它是“通配符”,表示能够匹配该位置上的任何名称。固然,若是我们要想使用aop
标签,就得先在配置文件中就得进行声明啦!此外,若是你们想进一步了解切入点pointcut
的表达式的话,能够参考博文“ Spring 框架中切入点 pointcut 表达式的经常使用写法 ”。