Spring中的攔截器
在web開發(fā)中胀莹,攔截器是經(jīng)常用到的功能议蟆。它可以幫我們驗證是否登陸、權(quán)限認(rèn)證歹篓、數(shù)據(jù)校驗瘫证、預(yù)先設(shè)置數(shù)據(jù)以及統(tǒng)計方法的執(zhí)行效率等等揉阎。今天就來詳細(xì)的談一下spring中的攔截器。spring中攔截器主要分種背捌,一個是HandlerInterceptor毙籽,一個是MethodInterceptor。
一毡庆、HandlerInterceptor攔截器
HandlerInterceptor是springMVC項目中的攔截器坑赡,它攔截的目標(biāo)是請求的地址,比MethodInterceptor先執(zhí)行么抗。實現(xiàn)一個HandlerInterceptor攔截器可以直接實現(xiàn)HandlerInterceptor接口毅否,也可以繼承HandlerInterceptorAdapter類。這兩種方法殊途同歸蝇刀,其實HandlerInterceptorAdapter也就是聲明了HandlerInterceptor接口中所有方法的默認(rèn)實現(xiàn)螟加,而我們在繼承他之后只需要重寫必要的方法。下面就是HandlerInterceptorAdapter的代碼吞琐,可以看到一個方法只是默認(rèn)返回true捆探,另外兩個是空方法:
/** * 自定義攔截器-基于springmvc
* @ClassName: CustomInterceptor
* @Description: springMVC項目中的攔截器,它攔截的目標(biāo)是請求的地址站粟,比MethodInterceptor先執(zhí)行黍图。
* 該攔截器只能過濾action請求,SPring允許多個攔截器同時存在奴烙,通過攔截器鏈管理助被。
* 當(dāng)preHandle return true時,執(zhí)行下一個攔截器缸沃,直到所有攔截器執(zhí)行完恰起,再運(yùn)行被攔截的請求。
* 當(dāng)preHandle return false時, 不再執(zhí)行后續(xù)的攔截器鏈及被攔截的請求趾牧。
* @author OnlyMate
* @Date 2018年8月28日 下午2:30:22
* */
public class CustomInterceptor implements HandlerInterceptor {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // TODO Auto-generated method stub
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception { // TODO Auto-generated method stub
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // TODO Auto-generated method stub
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
這三個方法都是干什么的检盼,有什么作用,什么時候調(diào)用翘单,不同的攔截器之間是怎樣的調(diào)用順序呢吨枉?這還得參考一下DispatcherServlet的doDispatch方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
代碼有點長,但是它封裝了springMVC處理請求的整個過程哄芜。首先根據(jù)請求找到對應(yīng)的HandlerExecutionChain貌亭,它包含了處理請求的handler和所有的HandlerInterceptor攔截器;然后在調(diào)用hander之前分別調(diào)用每個HandlerInterceptor攔截器的preHandle方法认臊,若有一個攔截器返回false圃庭,則會調(diào)用triggerAfterCompletion方法,并且立即返回不再往下執(zhí)行;若所有的攔截器全部返回true并且沒有出現(xiàn)異常剧腻,則調(diào)用handler返回ModelAndView對象拘央;再然后分別調(diào)用每個攔截器的postHandle方法;最后书在,即使是之前的步驟拋出了異常灰伟,也會執(zhí)行triggerAfterCompletion方法。關(guān)于攔截器的處理到此為止儒旬,接下來看看triggerAfterCompletion做了什么
private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, ex);
}
throw ex;
}
根據(jù)以上的代碼栏账,分析一下不同攔截器及其方法的執(zhí)行順序。假設(shè)有5個攔截器編號分別為12345栈源,若一切正常則方法的執(zhí)行順序是12345的preHandle挡爵,54321的postHandle,54321的afterCompletion凉翻。若編號3的攔截器的preHandle方法返回false或者拋出了異常了讨,接下來會執(zhí)行的是21的afterCompletion方法捻激。這里要注意的地方是制轰,我們在寫一個攔截器的時候要謹(jǐn)慎的處理preHandle中的異常,因為這里一旦有異常拋出就不會再受到這個攔截器的控制胞谭。12345的preHandle的方法執(zhí)行過之后垃杖,若handler出現(xiàn)了異常或者某個攔截器的postHandle方法出現(xiàn)了異常丈屹,則接下來都會執(zhí)行54321的afterCompletion方法调俘,因為只要12345的preHandle方法執(zhí)行完,當(dāng)前攔截器的攔截器就會記錄成編號5的攔截器旺垒,而afterCompletion總是從當(dāng)前的攔截器逆向的向前執(zhí)行彩库。
另外,實現(xiàn)HandlerInterceptor攔截器還有一個方法先蒋,就是實現(xiàn)WebRequestInterceptor接口骇钦。其實它和剛才的兩種方法也是殊途同歸,最終還是被spring適配成HandlerInterceptor竞漾。有一點不同眯搭,它的preHandle方法最終只會返回true。
這里可以根據(jù)自己的需求在對應(yīng)方法中寫自己業(yè)務(wù)處理邏輯
/**
* 自定義攔截器-基于springmvc
* @ClassName: CustomInterceptor
* @Description: springMVC項目中的攔截器业岁,它攔截的目標(biāo)是請求的地址鳞仙,比MethodInterceptor先執(zhí)行。
* 該攔截器只能過濾action請求笔时,SPring允許多個攔截器同時存在棍好,通過攔截器鏈管理。
* 當(dāng)preHandle return true時,執(zhí)行下一個攔截器借笙,直到所有攔截器執(zhí)行完爹梁,再運(yùn)行被攔截的請求。
* 當(dāng)preHandle return false時, 不再執(zhí)行后續(xù)的攔截器鏈及被攔截的請求提澎。
* @author OnlyMate
* @Date 2018年8月28日 下午2:30:22
*
*/
public class CustomInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(CustomInterceptor.class);
/**
* 在請求處理之前執(zhí)行姚垃,主要用于權(quán)限驗證、參數(shù)過濾等
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
logger.info("CustomInterceptor ==> preHandle method: do request before");
return true;
}
/**
* 當(dāng)前請求進(jìn)行處理之后執(zhí)行盼忌,主要用于日志記錄积糯、權(quán)限檢查、性能監(jiān)控谦纱、通用行為等
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
logger.info("CustomInterceptor ==> postHandle method: do request after");
}
/**
* 當(dāng)前對應(yīng)的interceptor的perHandle方法的返回值為true時,postHandle執(zhí)行完成并渲染頁面后執(zhí)行看成,主要用于資源清理工作
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
logger.info("CustomInterceptor ==> afterCompletion method: do request finshed");
}
}
配置如下:
/**
* Web MVC 配置適配器
* @ClassName: WebAppConfigurer
* @Description:
* @author OnlyMate
* @Date 2018年8月28日 下午2:39:31
*
* WebAppConfigurer extends WebMvcConfigurerAdapter 在Spring Boot2.0版本已過時了,用官網(wǎng)說的新的類替換
*
*/
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
/**
* 注入自定義攔截器
* @Title: addInterceptors
* @Description: 先add的攔截器會越靠外跨嘉,即越靠近瀏覽器
* @Date 2018年8月28日 下午2:47:28
* @author OnlyMate
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
WebMvcConfigurer.super.addInterceptors(registry);
registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/**");//攔截所有請求
}
}
二川慌、MethodInterceptor攔截器
MethodInterceptor是AOP項目中的攔截器,它攔截的目標(biāo)是方法祠乃,即使不是controller中的方法梦重。實現(xiàn)MethodInterceptor攔截器大致也分為兩種,一種是實現(xiàn)MethodInterceptor接口亮瓷,另一種利用AspectJ的注解或配置琴拧。
1、實現(xiàn)MethodInterceptor接口
/**
* 自定義攔截器-方法攔截器嘱支,基于spring aop
* @ClassName: CustomMethodInterceptor
* @Description: AOP項目中的攔截器蚓胸,它攔截的目標(biāo)是方法
* 配置在applicationContext.xml中
* @author OnlyMate
* @Date 2018年8月29日 下午3:35:24
*
*/
public class CustomMethodInterceptor implements MethodInterceptor {
private Logger logger = LoggerFactory.getLogger(CustomMethodInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
logger.info("CustomMethodInterceptor ==> invoke method: process method name is {}", invocation.getMethod().getName());
//TODO 處理操作
return invocation.proceed();
}
}
配置說明
<bean id="customMethodInterceptor" class="com.onlymate.springboot.interceptor.CustomMethodInterceptor" />
<aop:config proxy-target-class="false">
<!-- 方法攔截器,基于spring aop 實現(xiàn)配置 -->
<!-- 掃描使用了注解的方法進(jìn)行攔截 -->
<aop:advisor pointcut="@annotation(com.onlymate.springboot.annotation.CustomAnnotation)" advice-ref="customMethodInterceptor" />
<!-- 指定包路徑下的方法 -->
<aop:advisor pointcut="execution(* com.onlymate.springboot.controller.*.*(..))" advice-ref="customMethodInterceptor" />
</aop:config>
CustomAnnotation自定義注解
/**
* 自定義注解對象
* @ClassName: TableSplit
* @Description: TODO
* @author OnlyMate
* @Date 2018年5月22日 上午11:43:57
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
/** 需攔截方法名描述 */
String name() default "";
/** 加密 */
String[] encrypt() default {};
/** 解密 */
String[] decrypt() default {};
}
2除师、利用AspectJ的注解或配置
a沛膳、基于AspectJ注解
/**
* 自定義攔截器-方法攔截器,基于注解的AspectJ方式
* @ClassName: CustomAutoAspectJInterceptor
* @Description: 配置在applicationContext.xml中
* @author OnlyMate
* @Date 2018年8月29日 下午4:03:49
*
*/
@Component
@Aspect
public class CustomAutoAspectJInterceptor {
private Logger logger = LoggerFactory.getLogger(CustomAutoAspectJInterceptor.class);
@Around("execution (* com.onlymate.springboot.controller.*.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable{
logger.info("CustomAutoAspectJInterceptor ==> invoke method: process method class is {}", point.getTarget().getClass());
//TODO 處理操作
return point.proceed();
}
}
b汛聚、基于AspectJ配置
/**
* 自定義攔截器-方法攔截器锹安,基于AspectJ方式
* @ClassName: CustomAspectJInterceptor
* @Description: 配置在applicationContext.xml中
* @author OnlyMate
* @Date 2018年8月29日 下午4:03:49
*
*/
public class CustomAspectJInterceptor {
private Logger logger = LoggerFactory.getLogger(CustomAspectJInterceptor.class);
public Object around(ProceedingJoinPoint point) throws Throwable{
logger.info("CustomAspectJInterceptor ==> invoke method: process method class is {}", point.getTarget().getClass());
//TODO 處理操作
return point.proceed();
}
}
c、配置說明
<bean id="customAspectJInterceptor" class="com.onlymate.springboot.interceptor.CustomAspectJInterceptor"/>
<aop:config proxy-target-class="false">
<!-- 方法攔截器贞岭,基于AspectJ實現(xiàn)方式一 -->
<aop:aspect ref="customAspectJInterceptor">
<aop:around method="around" pointcut="execution(* com.onlymate.springboot.controller.*.*(..))"/>
</aop:aspect>
</aop:config>
<!-- 方法攔截器八毯,基于AspectJ實現(xiàn)方式二 -->
<!-- 自動掃描使用了aspectj注解的類 -->
<aop:aspectj-autoproxy/>
三、效果圖
四瞄桨、談一談區(qū)別
上面的兩種攔截器都能起到攔截的效果话速,但是他們攔截的目標(biāo)不一樣,實現(xiàn)的機(jī)制不同芯侥,所以有的時候適用不同的場景泊交。HandlerInterceptoer攔截的是請求地址乳讥,所以針對請求地址做一些驗證、預(yù)處理等操作比較合適廓俭。當(dāng)你需要統(tǒng)計請求的響應(yīng)時間時MethodInterceptor將不太容易做到云石,因為它可能跨越很多方法或者只涉及到已經(jīng)定義好的方法中一部分代碼。MethodInterceptor利用的是AOP的實現(xiàn)機(jī)制研乒,在本文中只說明了使用方式汹忠,關(guān)于原理和機(jī)制方面介紹的比較少,因為要說清楚這些需要講出AOP的相當(dāng)一部分內(nèi)容雹熬。在對一些普通的方法上的攔截HandlerInterceptoer就無能為力了宽菜,這時候只能利用AOP的MethodInterceptor。利用MethodInterceptor就可以很容易的實現(xiàn)一個日志攔截處理竿报。
另外铅乡,還有一個跟攔截器類似的東西----Filter。Filter是Servlet規(guī)范規(guī)定的烈菌,不屬于spring框架阵幸,也是用于請求的攔截。但是它適合更粗粒度的攔截芽世,在請求前后做一些編解碼處理挚赊、日志記錄等。而攔截器則可以提供更細(xì)粒度的捂襟,更加靈活的咬腕,針對某些請求欢峰、某些方法的組合的解決方案葬荷。