Filter(過濾器)节榜、interceptor(攔截器)和Aspect(切面)的使用及區(qū)別

在工作中如何選擇攔截機(jī)制去處理我們的業(yè)務(wù)請求,過濾器别智,攔截器宗苍,還是切面的選擇一直比較模糊,今天花時(shí)間整理一下

概述

1薄榛,F(xiàn)ilter

首先讳窟,過濾器是服務(wù)端的一個(gè)組件,是基于servlet實(shí)現(xiàn)從客戶端訪問服務(wù)端web資源的一種攔截機(jī)制敞恋,對請求request和響應(yīng)response都進(jìn)行過濾丽啡,依賴于serverlet容器,使用時(shí)硬猫,實(shí)現(xiàn)Filter接口补箍,在web.xml里配置對應(yīng)的class還有mapping-url,springboot工程可以通FilterRegisteration配置后,設(shè)置要過濾的URL啸蜜, 注意 兩種方式過濾器都是有序的坑雅,誰在前就先調(diào)用誰!定義過濾器后會(huì)重寫三個(gè)方法衬横,分別是init(),doFilter(),和destory(),

  • init方法是過濾器的初始化方法裹粤,當(dāng)web容器創(chuàng)建這個(gè)bean的時(shí)候就會(huì)執(zhí)行,這個(gè)方法可以讀取web.xml里面的參數(shù)
  • doFilter方法是執(zhí)行過濾的請求的核心蜂林,當(dāng)客戶端請求訪問web資源時(shí)遥诉,這個(gè)時(shí)候我們可以拿到request里面的參數(shù)拇泣,對數(shù)據(jù)進(jìn)行處理后,通過filterChain方法將請求將請求放行矮锈,放行后我們也可以通過response對響應(yīng)進(jìn)行處理(比如壓縮響應(yīng))挫酿,然后會(huì)傳遞到下一個(gè)過濾器
  • destory方法是當(dāng)web容器中的過濾器實(shí)例被銷毀時(shí),會(huì)被執(zhí)行愕难,釋放資源
/**
 * @author wtzhouc@gmail.com
 * @date 2019-04-12 19:36
 */
//@Component
public class TimeFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("過濾器初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("過濾器執(zhí)行了");
        long start2 = System.currentTimeMillis();
        filterChain.doFilter(servletRequest, servletResponse);
        long time = System.currentTimeMillis() - start2;
        System.out.println("過濾器執(zhí)行的時(shí)間是 :" + time);
        System.out.println("過濾器執(zhí)行結(jié)束");
    }

    @Override
    public void destroy() {
        System.out.println("過濾器銷毀了");
    }
}</pre>

spring boot工程可以通過加@Component注解添加進(jìn)spring管理,也可以通過下面注冊的方式去執(zhí)行惫霸,推薦用下方的方式

@Bean
public FilterRegistrationBean timeFilter() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    TimeFilter filter = new TimeFilter();
    filterRegistrationBean.setFilter(filter);
    filterRegistrationBean.addUrlPatterns("/user","/users");
    return filterRegistrationBean;
}
    接下來猫缭,我們可以看下圖示的過濾器執(zhí)行流程和生命周期,就能很好的理解Filter了

流程圖


image.png

生命周期


image.png
2壹店,interceptor(攔截器)

攔截器猜丹,顧名思義,他的作用就是攔截硅卢,這個(gè)要和過濾器區(qū)分開射窒,過濾器依賴serverlet容器,獲取request和response處理将塑,是基于函數(shù)回調(diào)脉顿,簡單說就是“去取你想取的”,攔截器是通過java反射機(jī)制点寥,動(dòng)態(tài)代理來攔截web請求艾疟,是“拒你想拒絕的”,他只攔截web請求敢辩,但不攔截靜態(tài)資源蔽莱,Struts2里面就是將攔截器串聯(lián),實(shí)現(xiàn)對請求的處理戚长,下面以spring 的攔截器為例盗冷,寫個(gè)demo

/**
 * @author wtzhouc@gmail.com
 * @date 2019-04-13 14:50
 */
@Component
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object hanlder) {
        out.println("攔截器.preHandle 開始執(zhí)行。同廉。仪糖。");
        out.println(hanlder.getClass().getSimpleName());
        out.println(((HandlerMethod) hanlder).getBean().getClass().getName());
        httpServletRequest.setAttribute("start", currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object hanlder, ModelAndView modelAndView) {
        out.println("攔截器.postHandle 開始執(zhí)行。迫肖。乓诽。");
        long start = (long) httpServletRequest.getAttribute("start");
        out.println("postHandle執(zhí)行時(shí)間為:" + (currentTimeMillis() - start));

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object hanlder, Exception e) {
        //會(huì)打印兩次 spring里面的basic error 也會(huì)被攔截
        out.println("攔截器.afterCompletion 開始執(zhí)行。咒程。鸠天。");
        long start = (long) httpServletRequest.getAttribute("start");
        out.println("afterCompletion執(zhí)行時(shí)間為:" + (currentTimeMillis() - start));
        out.println("\n ex is :" + e+"\n");
    }
}

再來看看攔截器再spring boot里面的配置,首先要繼承WebMvcConfigurerAdapter適配器帐姻,重寫addIntercepors方法再調(diào)用register方法添加攔截器稠集,前提奶段,自定義的interceptor要加上spring注解被spring容器管理

/**
 * @author wtzhouc@gmail.com
 * @date 2019-04-13 14:19
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    private final MyInterceptor myInterceptor;

    @Autowired
    public WebConfig(MyInterceptor myInterceptor) {
        this.myInterceptor = myInterceptor;
    }

//    @Bean
//    public FilterRegistrationBean timeFilter() {
//        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
//        TimeFilter filter = new TimeFilter();
//        filterRegistrationBean.setFilter(filter);
//        filterRegistrationBean.addUrlPatterns("/user","/users");
//        return filterRegistrationBean;
//    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor);
    }
}

    我們再來看看控制臺(tái)的輸出
image.png

接下來來講講攔截器的三個(gè)方法,preHandle(),postHandle(),afterCompletion()

??:攔截器與過濾器方法內(nèi)參數(shù)不同,多了一個(gè)Object handler,在請求進(jìn)入控制層前剥纷,spring mvc 會(huì)將請求交給handler處理痹籍,handler參數(shù)就是用來描述處理請求的,從上面的demo可以看出來 他的類型是handlerMethod的晦鞋,處理的請求的是BrowserAuthenticationController

  • preHandler(): 這個(gè)方法是在controller調(diào)用之前調(diào)用蹲缠,通過返回true或者false決定是否進(jìn)入Controller層
  • postHandler():在請求進(jìn)入控制層之后調(diào)用,但是在處理請求拋出異常時(shí)不會(huì)調(diào)用
  • afterCompletion(): 在請求處理完成之后悠垛,也就是在DispatherServlet渲染了視圖之后執(zhí)行线定,也就是說這個(gè)方法必定是執(zhí)行,包含異常信息确买,它的主要作用就是清理資源

接下來總結(jié)一下過濾器和攔截的前后順序斤讥,看下圖:


image.png
3,Aspect(切片)

在使用過濾器的時(shí)能獲取request和response對象湾趾,對請求和響應(yīng)進(jìn)行處理芭商,使用攔截器時(shí),我們可以通過handler來獲取當(dāng)前請求控制器的方法名稱搀缠,但是有一個(gè)弊端铛楣,我們拿不到控制器要接收的參數(shù),先看下servlet源碼的執(zhí)行順序

image.png

image.png

image.png

從DispatherServlet分發(fā)請求時(shí)艺普,進(jìn)入doService()方法內(nèi)部蛉艾,在方法參數(shù)封裝之前,添加了判斷衷敌,applyPreHandle()方法就時(shí)判斷攔截器里面的preHandler()方法勿侯,根據(jù)返回的true或者false,判斷是否執(zhí)行真正的handler缴罗,所以我們在攔截器的handler參數(shù)里面是獲取不到請求的參數(shù)的助琐,因此,我們要引入Spring AOP面氓,也就是切片編程兵钮,它可以在控制器的執(zhí)行之前,執(zhí)行之后舌界,拋出異常等等掘譬,進(jìn)行控制!

切片編程呻拌,在網(wǎng)上看到了一個(gè)很貼切的說法葱轩,面對的是處理過程中的方法或者階段,以獲得各部分的低耦合性的隔離效果,它是基于動(dòng)態(tài)代理靴拱,它關(guān)注的是行為和過程垃喊,它常用的注解為,下面通過spring boot 實(shí)現(xiàn)一個(gè)demo

  • @Aspect(聲明一個(gè)切面)
  • @Before(相當(dāng)于攔截器preHandler袜炕,在方法執(zhí)行前調(diào)用)
  • @After(相當(dāng)于攔截器的afterComplement()在方法執(zhí)行后調(diào)用)
  • @AfterThrowing(方法拋出異常時(shí)調(diào)用)
  • @AfterReturning(當(dāng)方法返回時(shí)調(diào)用)
  • @Around(包含以上方的執(zhí)行順序)
/**
 * @author wtzhouc@gmail.com
 * @date 2019-04-13 16:31
 */
@Aspect
@Component
public class TimeAspect {

    @Around("execution(* com.wtzhou.security.controller.UserController.*(..))")
//    @After("")
//    @Before("")
//    @AfterThrowing()
//    @AfterReturning()
    public Object handlerControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object[] args = proceedingJoinPoint.getArgs();
        for (Object arg : args) {
            out.println("請求參數(shù)為:"+arg);
        }
        out.println("TimeAspect 切片開始執(zhí)行");
        long start = currentTimeMillis();
        Object proceed = proceedingJoinPoint.proceed();
        out.println("切片執(zhí)行耗時(shí):" + (currentTimeMillis() - start));
        out.println("切片執(zhí)行結(jié)束本谜!");
        return proceed;
    }
}

通過注解內(nèi)部的表達(dá)式不同,可以定義你想要的切入點(diǎn)偎窘,ProceedingJoinPoint對象可以當(dāng)前的請求參數(shù)乌助,對參數(shù)處理后,可以調(diào)用proceed方法放行陌知,我們可以看看控制臺(tái)的輸出

image.png

image.png
總結(jié)一下
在代碼里面將過濾器他托,攔截器,切片纵诞,還有我們常用的@ControllerAdvice異常攔截機(jī)制注解放開時(shí),我們來看看控制臺(tái)的輸出
image.png
    通過控制臺(tái)的日志 我們可以用一張簡單的圖來直觀展現(xiàn)出來
image.png

通過圖示:當(dāng)收到請求響應(yīng)時(shí)培遵,執(zhí)行的順序?yàn)閒ilter--》interceptor--》ControllerAdvice--》Aspect浙芙,然后到大控制層,如果控制層拋出異常籽腕,最先也會(huì)被Aspect捕獲嗡呼,如果未處理,會(huì)繼續(xù)向上一層拋出皇耗,如果到Filter也沒有處理的話南窗,就會(huì)拋到容器內(nèi)部

===== 結(jié)束 =====

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市郎楼,隨后出現(xiàn)的幾起案子万伤,更是在濱河造成了極大的恐慌,老刑警劉巖呜袁,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敌买,死亡現(xiàn)場離奇詭異,居然都是意外死亡阶界,警方通過查閱死者的電腦和手機(jī)虹钮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膘融,“玉大人芙粱,你說我怎么就攤上這事⊙跤常” “怎么了春畔?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我拐迁,道長蹭劈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任线召,我火速辦了婚禮铺韧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缓淹。我一直安慰自己哈打,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布讯壶。 她就那樣靜靜地躺著料仗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伏蚊。 梳的紋絲不亂的頭發(fā)上立轧,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音躏吊,去河邊找鬼氛改。 笑死,一個(gè)胖子當(dāng)著我的面吹牛比伏,可吹牛的內(nèi)容都是我干的胜卤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼赁项,長吁一口氣:“原來是場噩夢啊……” “哼葛躏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起悠菜,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤舰攒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后悔醋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芒率,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年篙顺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了偶芍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡德玫,死狀恐怖匪蟀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宰僧,我是刑警寧澤材彪,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響段化,放射性物質(zhì)發(fā)生泄漏嘁捷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一显熏、第九天 我趴在偏房一處隱蔽的房頂上張望雄嚣。 院中可真熱鬧,春花似錦喘蟆、人聲如沸缓升。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽港谊。三九已至,卻和暖如春橙弱,著一層夾襖步出監(jiān)牢的瞬間歧寺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工棘脐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斜筐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓荆残,卻偏偏與公主長得像奴艾,于是被迫代替她去往敵國和親净当。 傳聞我的和親對象是個(gè)殘疾皇子内斯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345