Spring Boot實踐——三種攔截器的創(chuàng)建

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/>

三、效果圖

201908161453001

四瞄桨、談一談區(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ì)粒度的捂襟,更加靈活的咬腕,針對某些請求欢峰、某些方法的組合的解決方案葬荷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市纽帖,隨后出現(xiàn)的幾起案子宠漩,更是在濱河造成了極大的恐慌,老刑警劉巖懊直,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扒吁,死亡現(xiàn)場離奇詭異,居然都是意外死亡室囊,警方通過查閱死者的電腦和手機(jī)雕崩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來融撞,“玉大人盼铁,你說我怎么就攤上這事〕①耍” “怎么了饶火?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵鹏控,是天一觀的道長。 經(jīng)常有香客問我肤寝,道長当辐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任鲤看,我火速辦了婚禮缘揪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘义桂。我一直安慰自己寺晌,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布澡刹。 她就那樣靜靜地躺著呻征,像睡著了一般。 火紅的嫁衣襯著肌膚如雪罢浇。 梳的紋絲不亂的頭發(fā)上陆赋,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機(jī)與錄音嚷闭,去河邊找鬼攒岛。 笑死,一個胖子當(dāng)著我的面吹牛胞锰,可吹牛的內(nèi)容都是我干的灾锯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嗅榕,長吁一口氣:“原來是場噩夢啊……” “哼顺饮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凌那,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤兼雄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后帽蝶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赦肋,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年励稳,在試婚紗的時候發(fā)現(xiàn)自己被綠了佃乘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡驹尼,死狀恐怖趣避,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扶欣,我是刑警寧澤鹅巍,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布千扶,位于F島的核電站,受9級特大地震影響骆捧,放射性物質(zhì)發(fā)生泄漏澎羞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一敛苇、第九天 我趴在偏房一處隱蔽的房頂上張望妆绞。 院中可真熱鬧,春花似錦枫攀、人聲如沸括饶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽图焰。三九已至,卻和暖如春蹦掐,著一層夾襖步出監(jiān)牢的瞬間庵楷,已是汗流浹背琳状。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留撑蚌,地道東北人期奔。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓缕贡,卻偏偏與公主長得像效览,于是被迫代替她去往敵國和親双仍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容