Spring MVC 06:拦截器
什么是拦截器?
- 拦截器是Spring MVC中强大的控件,它可以在进入处理器(Controller)之前做一些操作,或者在处理器完成后进行操作,甚至是在渲染视图后进行操作。
就是对Controller的拦截
拦截器概述
- 对于任何优秀的MVC框架,都会提供一些通用的操作,如请求数据的封装、类型转换、数据校验、解析上传的文件、防止表单的多次提交等。早期的MVC框架将这些操作都写死在核心控制器中,而这些常用的操作又不是所有的请求都需要实现的,这就导致了框架的灵活性不足,可扩展性降低
- SpringMVC提供了Interceptor拦截器机制,类似于Servlet中的Filter过滤器,用于拦截用户的请求并做出相应的处理。比如通过拦截器来进行用户权限验证,或者用来判断用户是否已经登录。Spring MVC拦截器是可插拔式的设计,需要某一功能拦截器,只需在配置文件中应用该拦截器即可;如果不需要这个功能拦截器,只需在配置文件中取消应用该拦截器。
- 在Spring MVC中定义一个拦截器有两种方法:实现HandlerInterceptor接口,实现WebRequestInterceptor接口.
SpringMVC拦截器和JavaEE过滤器的区别
- 拦截器是SpringMVC特有的,只能拦截Controller的调用,而过滤器是对Servlet的拦截,以及可以通过Path的匹配拦截所有的静态资源,而拦截器做不到。
- 拦截器是基于SpringAOP实现的
Java 里的拦截器是动态拦截action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行,同时也提供了一种可以提取action中可重用部分的方式。在 AOP(Aspect-Oriented Programming,面向切面编程)中拦截器用于在某个方法(包括构造器)或字段被访问之前进行拦截,然后在之前或之后加入某些操作。特别地,现阶段 Spring 自身仅支持基于方法的拦截操作!如果基于方法的拦截操作不能满足需求,可以使用 AspectJ 与 Spring 进行集成,以实现更细粒度或更多方面的拦截操作。
拦截器原理
基于Java的动态代理实现
实现方法
在SpringMVC种,我们要想实现拦截器的功能,主要有两种方法,一种是实现HandlerInterceptor接口,第二种是实现WebRequestInterceptor接口。我们先用第一种方法来实现拦截器的功能。
HandlerInterceptor 接口
在HandlerInterceptor接口中,定义了 3 个方法,分别为preHandle()、postHandle()和afterCompletion(),我们就是通过复写这 3 个方法来对用户的请求进行拦截处理的。因此,我们可以通过直接实现HandlerInterceptor接口来实现拦截器的功能。不过在 Spring 框架之中,其还提供了另外一个接口和一个抽象类,实现了对HandlerInterceptor接口的功能扩展,分别为:AsyncHandlerInterceptor和HandlerInterceptorAdapter.
对于AsyncHandlerInterceptor接口,其在继承HandlerInterceptor接口的同时,又声明了一个新的方法afterConcurrentHandlingStarted();而HandlerInterceptorAdapter抽象类,则是更进一步,在其继承AsyncHandlerInterceptor接口的同时,又复写了preHandle方法。因此,AsyncHandlerInterceptor更像是一个过渡的接口。
接下来,我们先看一个配置拦截器的例子
新建一个类,MyInterceptor.java
public class MyInterceptor implements HandlerInterceptor {
// Controller调用之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle()");
return true;
}
// Controller调用之后
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle()");
}
// JSP运行完以后调用
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
给SpringMVC.xml添加配置
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/helloworld2/hello3"/> <!--要拦截的路径-->
<bean class="cn.silverCorridors.MyInterceptor"></bean><!--拦截类-->
</mvc:interceptor>
</mvc:interceptors>
这样就简单配置好了一个拦截器。注意拦截顺序
在实际应用中,我们一般都是通过实现HandlerInterceptor接口或者继承HandlerInterceptorAdapter抽象类,复写preHandle()、postHandle()和afterCompletion()这 3 个方法来对用户的请求进行拦截处理的。下面,我们就详细介绍这个 3 个方法。
preHandle(HttpServletRequest request, HttpServletResponse response, Object handle)方法,该方法在请求处理之前进行调用。Spring MVC 中的Interceptor是链式调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor。每个Interceptor的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor中的preHandle方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求做一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔(Boolean)类型的,当它返回为false时,表示请求结束,后续的Interceptor和控制器(Controller)都不会再执行;当返回值为true时,就会继续调用下一个Interceptor的preHandle方法,如果已经是最后一个Interceptor的时候,就会是调用当前请求的控制器中的方法。postHandle(HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView)方法,通过preHandle方法的解释,我们知道这个方法包括后面要说到的afterCompletion方法都只能在当前所属的Interceptor的preHandle方法的返回值为true的时候,才能被调用。postHandle方法在当前请求进行处理之后,也就是在控制器中的方法调用之后执行,但是它会在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在这个方法中对控制器处理之后的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渲染了对应的视图之后执行,这个方法的主要作用是用于进行资源清理的工作。
接下来,我们在看看以上接口和抽象类的具体代码:
HandlerInterceptor 接口:
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 接口:
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 {
}
}
WebRequestInterceptor 接口
在WebRequestInterceptor接口中也定义了 3 个方法,同HandlerInterceptor接口完全相同,也是通过复写这 3 个方法来对用户的请求进行拦截处理的。而且这 3 个方法都传递了同一个参数**WebRequest**,那么这个WebRequest到底是什么呢?其实这个WebRequest是 Spring 中定义的一个接口,它里面的方法定义跟HttpServletRequest类似,在WebRequestInterceptor中对WebRequest进行的所有操作都将同步到HttpServletRequest中,然后在当前请求中依次传递。
在 Spring 框架之中,还提供了一个和WebRequestInterceptor接口长的很像的抽象类,那就是:WebRequestInterceptorAdapter,其实现了AsyncHandlerInterceptor接口,并在内部调用了WebRequestInterceptor接口。
WebRequestInterceptor接口的 3 个函数:
preHandle(WebRequest request)方法,该方法在请求处理之前进行调用,也就是说,其会在控制器中的方法调用之前被调用。**这个方法跟HandlerInterceptor中的preHandle不同,主要区别在于该方法的返回值是void类型的,也就是没有返回值,因此我们主要用它来进行资源的准备工作,**比如我们在使用 Hibernate 的时候,可以在这个方法中准备一个 Hibernate 的Session对象,然后利用WebRequest的setAttribute(name, value, scope)把它放到WebRequest的属性中。在这里,进一步说说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)方法,该方法在请求处理之后,也就是在控制器中的方法调用之后被调用,但是会在视图返回被渲染之前被调用,所以可以在这个方法里面通过改变数据模型ModelMap来改变数据的展示。该方法有两个参数,WebRequest对象是用于传递整个请求数据的,比如在preHandle中准备的数据都可以通过WebRequest来传递和访问;ModelMap就是控制器处理之后返回的Model对象,我们可以通过改变它的属性来改变返回的Model模型。afterCompletion(WebRequest request, Exception ex)方法,该方法会在整个请求处理完成,也就是在视图返回并被渲染之后执行。因此可以在该方法中进行资源的释放操作。而WebRequest参数就可以把我们在preHandle中准备的资源传递到这里进行释放。Exception参数表示的是当前请求的异常对象,如果在控制器中抛出的异常已经被 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;
}
12345678910111213
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);
}
}
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
如上面的代码所示,展示了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......");
}
}
123456789101112131415161718192021222324252627
3.3 AbstractInterceptor 抽象类
除了上面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抽象类的源码。