SpringBoot實(shí)現(xiàn)過(guò)濾器瓦宜、攔截器與切片

整理自:https://mp.weixin.qq.com/s/OaMaT-QF20P65tKOjC5_oA

Q:使用過(guò)濾器秒梳、攔截器與切片實(shí)現(xiàn)每個(gè)請(qǐng)求耗時(shí)的統(tǒng)計(jì),并比較三者的區(qū)別與聯(lián)系

過(guò)濾器Filter

過(guò)濾器概念

Filter是J2E中來(lái)的剧防,可以看做是Servlet的一種“加強(qiáng)版”,它主要用于對(duì)用戶請(qǐng)求進(jìn)行預(yù)處理和后處理辫樱,擁有一個(gè)典型的處理鏈峭拘。Filter也可以對(duì)用戶請(qǐng)求生成響應(yīng),這一點(diǎn)與Servlet相同狮暑,但實(shí)際上很少會(huì)使用Filter向用戶請(qǐng)求生成響應(yīng)鸡挠。

使用Filter完整的流程是:Filter對(duì)用戶請(qǐng)求進(jìn)行預(yù)處理,接著將請(qǐng)求交給Servlet進(jìn)行預(yù)處理并生成響應(yīng)搬男,最后Filter再對(duì)服務(wù)器響應(yīng)進(jìn)行后處理拣展。

過(guò)濾器作用

在JavaDoc中給出了幾種過(guò)濾器的作用

  • Examples that have been identified for this design are
    1. Authentication Filters, 即用戶訪問(wèn)權(quán)限過(guò)濾
    2. Logging and Auditing Filters, 日志過(guò)濾,可以記錄特殊用戶的特殊請(qǐng)求的記錄等
    3. Image conversion Filters
    4. Data compression Filters
    5. Encryption Filters
    6. Tokenizing Filters
    7. Filters that trigger resource access events
    8. XSL/T filters
    9. Mime-type chain Filter

對(duì)于第一條缔逛,即使用Filter作權(quán)限過(guò)濾备埃,其可以這么實(shí)現(xiàn):定義一個(gè)Filter,獲取每個(gè)客戶端發(fā)起的請(qǐng)求URL褐奴,與當(dāng)前用戶無(wú)權(quán)限訪問(wèn)的URL列表(可以是從DB中取出)作對(duì)比按脚,起到權(quán)限過(guò)濾的作用。

過(guò)濾器實(shí)現(xiàn)方式

自定義的過(guò)濾器都必須實(shí)現(xiàn)javax.Servlet.Filter接口敦冬,并重寫(xiě)接口中定義的三個(gè)方法:

1.void init(FilterConfig config)

用于完成Filter的初始化辅搬。

2.void destory()

用于Filter銷毀前,完成某些資源的回收脖旱。

3.void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)

實(shí)現(xiàn)過(guò)濾功能堪遂,即對(duì)每個(gè)請(qǐng)求及響應(yīng)增加的額外的預(yù)處理和后處理。,執(zhí)行該方法之前萌庆,即對(duì)用戶請(qǐng)求進(jìn)行預(yù)處理溶褪;執(zhí)行該方法之后,即對(duì)服務(wù)器響應(yīng)進(jìn)行后處理践险。

值得注意的是猿妈,chain.doFilter()方法執(zhí)行之前為預(yù)處理階段,該方法執(zhí)行結(jié)束即代表用戶的請(qǐng)求已經(jīng)得到控制器處理捏境。因此于游,如果在doFilter中忘記調(diào)用chain.doFilter()方法,則用戶的請(qǐng)求將得不到處理垫言。

import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import java.io.IOException;// 必須添加注解贰剥,springmvc通過(guò)web.xml配置@Componentpublic class TimeFilter implements Filter {    private static final Logger LOG = LoggerFactory.getLogger(TimeFilter.class);    @Override    public void init(FilterConfig filterConfig) throws ServletException {        LOG.info("初始化過(guò)濾器:{}", filterConfig.getFilterName());    }    @Override    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {        LOG.info("start to doFilter");        long startTime = System.currentTimeMillis();        chain.doFilter(request, response);        long endTime = System.currentTimeMillis();        LOG.info("the request of {} consumes {}ms.", getUrlFrom(request), (endTime - startTime));        LOG.info("end to doFilter");    }    @Override    public void destroy() {        LOG.info("銷毀過(guò)濾器");    }    private String getUrlFrom(ServletRequest servletRequest){        if (servletRequest instanceof HttpServletRequest){            return ((HttpServletRequest) servletRequest).getRequestURL().toString();        }        return "";    }}

從代碼中可看出,類Filter是在javax.servlet.*中筷频,因此可以看出蚌成,過(guò)濾器的一個(gè)很大的局限性在于前痘,其不能夠知道當(dāng)前用戶的請(qǐng)求是被哪個(gè)控制器(Controller)處理的,因?yàn)楹笳呤莝pring框架中定義的担忧。

在SpringBoot中注冊(cè)第三方過(guò)濾器

對(duì)于SpringMvc芹缔,可以通過(guò)在web.xml中注冊(cè)過(guò)濾器。但在SpringBoot中不存在web.xml瓶盛,此時(shí)如果引用的某個(gè)jar包中的過(guò)濾器最欠,且這個(gè)過(guò)濾器在實(shí)現(xiàn)時(shí)沒(méi)有使用@Component標(biāo)識(shí)為Spring Bean,則這個(gè)過(guò)濾器將不會(huì)生效惩猫。

此時(shí)需要通過(guò)java代碼去注冊(cè)這個(gè)過(guò)濾器芝硬。以上面定義的TimeFilter為例,當(dāng)去掉類注解@Component時(shí)轧房,注冊(cè)方式為:

@Configurationpublic 
class WebConfig {    
/**     
* 注冊(cè)第三方過(guò)濾器     
* 功能與spring mvc中通過(guò)配置web.xml相同     
* @return     
*/    
@Bean    
public FilterRegistrationBean thirdFilter(){        
      ThirdPartFilter thirdPartFilter = new ThirdPartFilter();        
      FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean() ;        
      filterRegistrationBean.setFilter(thirdPartFilter);        
      List<String > urls = new ArrayList<>();        
          // 匹配所有請(qǐng)求路徑        
       urls.add("/*");        
     filterRegistrationBean.setUrlPatterns(urls);        
     return filterRegistrationBean;    
}}

相比使用@Component注解拌阴,這種配置方式有個(gè)優(yōu)點(diǎn),即可以自由配置攔截的URL奶镶。

攔截器Interceptor

攔截器概念

攔截器迟赃,在AOP(Aspect-Oriented Programming)中用于在某個(gè)方法或字段被訪問(wèn)之前,進(jìn)行攔截厂镇,然后在之前或之后加入某些操作纤壁。攔截是AOP的一種實(shí)現(xiàn)策略。

攔截器作用

  • 日志記錄:記錄請(qǐng)求信息的日志剪撬,以便進(jìn)行信息監(jiān)控摄乒、信息統(tǒng)計(jì)、計(jì)算PV(Page View)等

  • 權(quán)限檢查:如登錄檢測(cè)残黑,進(jìn)入處理器檢測(cè)檢測(cè)是否登錄

  • 性能監(jiān)控:通過(guò)攔截器在進(jìn)入處理器之前記錄開(kāi)始時(shí)間,在處理完后記錄結(jié)束時(shí)間斋否,從而得到該請(qǐng)求的處理時(shí)間梨水。(反向代理,如apache也可以自動(dòng)記錄)茵臭;

  • 通用行為:讀取cookie得到用戶信息并將用戶對(duì)象放入請(qǐng)求疫诽,從而方便后續(xù)流程使用,還有如提取Locale旦委、Theme信息等奇徒,只要是多個(gè)處理器都需要的即可使用攔截器實(shí)現(xiàn)。

攔截器實(shí)現(xiàn)

通過(guò)實(shí)現(xiàn)HandlerInterceptor接口缨硝,并重寫(xiě)該接口的三個(gè)方法來(lái)實(shí)現(xiàn)攔截器的自定義:

1.preHandler(HttpServletRequest request, HttpServletResponse response, Object handler)

方法將在請(qǐng)求處理之前進(jìn)行調(diào)用摩钙。SpringMVC中的Interceptor同F(xiàn)ilter一樣都是鏈?zhǔn)秸{(diào)用。每個(gè)Interceptor的調(diào)用會(huì)依據(jù)它的聲明順序依次執(zhí)行查辩,而且最先執(zhí)行的都是Interceptor中的preHandle方法胖笛,所以可以在這個(gè)方法中進(jìn)行一些前置初始化操作或者是對(duì)當(dāng)前請(qǐng)求的一個(gè)預(yù)處理网持,也可以在這個(gè)方法中進(jìn)行一些判斷來(lái)決定請(qǐng)求是否要繼續(xù)進(jìn)行下去。

該方法的返回值是布爾值Boolean 類型的长踊,當(dāng)它返回為false時(shí)功舀,表示請(qǐng)求結(jié)束,后續(xù)的Interceptor和Controller都不會(huì)再執(zhí)行身弊;當(dāng)返回值為true時(shí)就會(huì)繼續(xù)調(diào)用下一個(gè)Interceptor 的preHandle 方法辟汰,如果已經(jīng)是最后一個(gè)Interceptor 的時(shí)候就會(huì)是調(diào)用當(dāng)前請(qǐng)求的Controller 方法。

2.postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)

在當(dāng)前請(qǐng)求進(jìn)行處理之后阱佛,也就是Controller 方法調(diào)用之后執(zhí)行帖汞,但是它會(huì)在DispatcherServlet 進(jìn)行視圖返回渲染之前被調(diào)用,所以我們可以在這個(gè)方法中對(duì)Controller 處理之后的ModelAndView 對(duì)象進(jìn)行操作瘫絮。

3.afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)

該方法也是需要當(dāng)前對(duì)應(yīng)的Interceptor的preHandle方法的返回值為true時(shí)才會(huì)執(zhí)行涨冀。顧名思義,該方法將在整個(gè)請(qǐng)求結(jié)束之后麦萤,也就是在DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行鹿鳖。這個(gè)方法的主要作用是用于進(jìn)行資源清理工作的。

@Componentpublic 
class TimeInterceptor implements HandlerInterceptor {    
private static final Logger LOG = LoggerFactory.getLogger(TimeInterceptor.class);    
        @Override    
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        
         LOG.info("在請(qǐng)求處理之前進(jìn)行調(diào)用(Controller方法調(diào)用之前)");        
         request.setAttribute("startTime", System.currentTimeMillis());        
        HandlerMethod handlerMethod = (HandlerMethod) handler;        
        LOG.info("controller object is {}", handlerMethod.getBean().getClass().getName());        
        LOG.info("controller method is {}", handlerMethod.getMethod());        
       // 需要返回true壮莹,否則請(qǐng)求不會(huì)被控制器處理        
        return true;    
}    
@Override    
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        
          LOG.info("請(qǐng)求處理之后進(jìn)行調(diào)用翅帜,但是在視圖被渲染之前(Controller方法調(diào)用之后),如果異常發(fā)生命满,則 該方法不會(huì)被調(diào)用");   
 }    
@Override    
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        
       LOG.info("在整個(gè)請(qǐng)求結(jié)束之后被調(diào)用涝滴,也就是在DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行(主要是用于進(jìn)行資源清理工作)");        
        long startTime = (long) request.getAttribute("startTime");        
         LOG.info("time consume is {}", System.currentTimeMillis() - startTime);    
}

與過(guò)濾器不同的是,攔截器使用@Component修飾后胶台,在SpringBoot中還需要通過(guò)實(shí)現(xiàn)WebMvcConfigurer手動(dòng)注冊(cè):

// java配置類@Configurationpublic 
class WebConfig implements WebMvcConfigurer {    
       @Autowired    
       private TimeInterceptor timeInterceptor;    
        @Override    
       public void addInterceptors(InterceptorRegistry registry){       
        registry.addInterceptor(timeInterceptor);    
}}

如果是在SpringMVC中歼疮,則需要通過(guò)xml文件配置<mvc:interceptors>節(jié)點(diǎn)信息。

切片Aspect

切片概述

相比過(guò)濾器诈唬,攔截器能夠知道用戶發(fā)出的請(qǐng)求最終被哪個(gè)控制器處理韩脏,但是攔截器還有一個(gè)明顯的不足,即不能夠獲取request的參數(shù)以及控制器處理之后的response铸磅。所以就有了切片的用武之地了赡矢。

切片實(shí)現(xiàn)

切片的實(shí)現(xiàn)需要注意@Aspect,@Component以及@Around這三個(gè)注解的使用,詳細(xì)查看官方文檔:

https://docs.spring.io/spring/docs/5.0.12.RELEASE/spring-framework-reference/core.html#aop

@Aspect
@Componentpublic 
class TimeAspect {    
           private static final Logger LOG = LoggerFactory.getLogger(TimeAspect.class);    
          @Around("execution(* me.ifight.controller.*.*(..))")    
  public Object handleControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{        
          LOG.info("切片開(kāi)始阅仔。吹散。。");        
          long startTime = System.currentTimeMillis();        
          // 獲取請(qǐng)求入?yún)?       
          Object[] args = proceedingJoinPoint.getArgs();        
          Arrays.stream(args).forEach(arg -> LOG.info("arg is {}", arg));        
         // 獲取相應(yīng)        
         Object response = proceedingJoinPoint.proceed();        
         long endTime = System.currentTimeMillis();        
         LOG.info("請(qǐng)求:{}, 耗時(shí){}ms", proceedingJoinPoint.getSignature(), (endTime - startTime));        
         LOG.info("切片結(jié)束八酒。空民。。");        
           return null;    
}}

過(guò)濾器丘跌、攔截器以及切片的調(diào)用順序

如下圖袭景,展示了三者的調(diào)用順序Filter->Intercepto->Aspect->Controller唁桩。相反的是,當(dāng)Controller拋出的異常的處理順序則是從內(nèi)到外的耸棒。因此我們總是定義一個(gè)注解@ControllerAdvice去統(tǒng)一處理控制器拋出的異常荒澡。

如果一旦異常被@ControllerAdvice處理了,則調(diào)用攔截器的afterCompletion方法的參數(shù)Exception ex就為空了与殃。

image.png

實(shí)際執(zhí)行的調(diào)用棧也說(shuō)明了這一點(diǎn):

image

而對(duì)于過(guò)濾器和攔截器詳細(xì)的調(diào)用順序如下圖:

image

過(guò)濾器和攔截器的區(qū)別

最后有必要再說(shuō)說(shuō)過(guò)濾器和攔截器二者之間的區(qū)別:

image

除此之外单山,相比過(guò)濾器,攔截器能夠“看到”用戶的請(qǐng)求具體是被Spring框架的哪個(gè)控制器所處理幅疼。

參考

https://blog.csdn.net/xiaodanjava/article/details/32125687

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末米奸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子爽篷,更是在濱河造成了極大的恐慌悴晰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逐工,死亡現(xiàn)場(chǎng)離奇詭異铡溪,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)泪喊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門棕硫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人袒啼,你說(shuō)我怎么就攤上這事哈扮。” “怎么了蚓再?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵滑肉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我摘仅,道長(zhǎng)赦邻,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任实檀,我火速辦了婚禮,結(jié)果婚禮上按声,老公的妹妹穿的比我還像新娘膳犹。我一直安慰自己,他們只是感情好签则,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布须床。 她就那樣靜靜地躺著,像睡著了一般渐裂。 火紅的嫁衣襯著肌膚如雪豺旬。 梳的紋絲不亂的頭發(fā)上钠惩,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音族阅,去河邊找鬼篓跛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛坦刀,可吹牛的內(nèi)容都是我干的愧沟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼鲤遥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沐寺!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起盖奈,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤混坞,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后钢坦,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體究孕,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年场钉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蚊俺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逛万,死狀恐怖泳猬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宇植,我是刑警寧澤得封,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站指郁,受9級(jí)特大地震影響忙上,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜闲坎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一疫粥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腰懂,春花似錦梗逮、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春底哗,著一層夾襖步出監(jiān)牢的瞬間岁诉,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工跋选, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涕癣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓野建,卻偏偏與公主長(zhǎng)得像属划,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子候生,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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