SpringBoot實(shí)現(xiàn)過濾器、攔截器與切片的實(shí)現(xiàn)和區(qū)別
Q:使用過濾器、攔截器與切片實(shí)現(xiàn)每個(gè)請(qǐng)求耗時(shí)的統(tǒng)計(jì),并比較三者的區(qū)別與聯(lián)系
過濾器Filter
過濾器概念
Filter是J2E中來的,可以看做是 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)行后處理。
過濾器作用
在JavaDoc中給出了幾種過濾器的作用
* Examples that have been identified for this design are<br> * 1) Authentication Filters, 即用戶訪問權(quán)限過濾 * 2) Logging and Auditing Filters, 日志過濾,可以記錄特殊用戶的特殊請(qǐng)求的記錄等 * 3) Image conversion Filters * 4) Data compression Filters <br> * 5) Encryption Filters <br> * 6) Tokenizing Filters <br> * 7) Filters that trigger resource access events <br> * 8) XSL/T filters <br> * 9) Mime-type chain Filter <br>
對(duì)于第一條,即使用Filter作權(quán)限過濾,其可以這么實(shí)現(xiàn):定義一個(gè)Filter,獲取每個(gè)客戶端發(fā)起的請(qǐng)求URL,與當(dāng)前用戶無權(quán)限訪問的URL列表(可以是從DB中取出)作對(duì)比,起到權(quán)限過濾的作用。
過濾器實(shí)現(xiàn)方式
自定義的過濾器都必須實(shí)現(xiàn) javax.Servlet.Filter
接口,并重寫接口中定義的三個(gè)方法:
1、void init(FilterConfig config)
用于完成Filter的初始化。
2、void destory()
用于Filter銷毀前,完成某些資源的回收。
3、void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
實(shí)現(xiàn)過濾功能,即對(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通過web.xml配置 @Component public class TimeFilter implements Filter { private static final Logger LOG = LoggerFactory.getLogger(TimeFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { LOG.info("初始化過濾器:{}", 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("銷毀過濾器"); } private String getUrlFrom(ServletRequest servletRequest){ if (servletRequest instanceof HttpServletRequest){ return ((HttpServletRequest) servletRequest).getRequestURL().toString(); } return ""; } }
從代碼中可看出,類 Filter
是在 javax.servlet.*
中,因此可以看出,過濾器的一個(gè)很大的局限性在于, 其不能夠知道當(dāng)前用戶的請(qǐng)求是被哪個(gè)控制器(Controller)處理的 ,因?yàn)楹笳呤莝pring框架中定義的。
在SpringBoot中注冊(cè)第三方過濾器
對(duì)于SpringMvc,可以通過在 web.xml
中注冊(cè)過濾器。但在SpringBoot中不存在 web.xml
,此時(shí)如果引用的某個(gè)jar包中的過濾器,且這個(gè)過濾器在實(shí)現(xiàn)時(shí)沒有使用 @Component
標(biāo)識(shí)為Spring Bean,則這個(gè)過濾器將不會(huì)生效。此時(shí)需要通過java代碼去注冊(cè)這個(gè)過濾器。以上面定義的 TimeFilter
為例,當(dāng)去掉類注解 @Component
時(shí),注冊(cè)方式為:
@Configuration public class WebConfig { /** * 注冊(cè)第三方過濾器 * 功能與spring mvc中通過配置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è)方法或字段被訪問之前,進(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)控:通過攔截器在進(jìn)入處理器之前記錄開始時(shí)間,在處理完后記錄結(jié)束時(shí)間,從而得到該請(qǐng)求的處理時(shí)間。(反向代理,如apache也可以自動(dòng)記錄);
- 通用行為:讀取cookie得到用戶信息并將用戶對(duì)象放入請(qǐng)求,從而方便后續(xù)流程使用,還有如提取Locale、Theme信息等,只要是多個(gè)處理器都需要的即可使用攔截器實(shí)現(xiàn)。
攔截器實(shí)現(xiàn)
通過實(shí)現(xiàn) HandlerInterceptor
接口,并重寫該接口的三個(gè)方法來實(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)行一些判斷來決定請(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)行資源清理工作的。
@Component public 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); }
與過濾器不同的是,攔截器使用 @Component
修飾后,還需要通過實(shí)現(xiàn) WebMvcConfigurer
手動(dòng)注冊(cè):
// java配置類 @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private TimeInterceptor timeInterceptor; @Override public void addInterceptors(InterceptorRegistry registry){ registry.addInterceptor(timeInterceptor); } }
切片Aspect
切片概述
相比過濾器,攔截器能夠知道用戶發(fā)出的請(qǐng)求最終被哪個(gè)控制器處理,但是攔截器還有一個(gè)明顯的不足,即不能夠獲取request的參數(shù)以及控制器處理之后的response。所以就有了切片的用武之地了。
切片實(shí)現(xiàn)
切片的實(shí)現(xiàn)需要注意 @Aspect
, @Component
以及 @Around
這三個(gè)注解的使用,詳細(xì)查看官方文檔:傳送門
@Aspect @Component public 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("切片開始。。。"); 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; } }
過濾器、攔截器以及切片的調(diào)用順序
如下圖,展示了三者的調(diào)用順序Filter->Intercepto->Aspect->Controller。相反的是,當(dāng)Controller拋出的異常的處理順序則是從內(nèi)到外的。因此我們總是定義一個(gè)注解 @ControllerAdvice
去統(tǒng)一處理控制器拋出的異常。如果一旦異常被 @ControllerAdvice
處理了,則調(diào)用攔截器的 afterCompletion
方法的參數(shù) Exception ex
就為空了。
實(shí)際執(zhí)行的調(diào)用棧也說明了這一點(diǎn):
而對(duì)于過濾器和攔截器詳細(xì)的調(diào)用順序如下圖:
過濾器和攔截器的區(qū)別
最后有必要再說說過濾器和攔截器二者之間的區(qū)別:
Filter | Interceptor | |
---|---|---|
實(shí)現(xiàn)方式 | 過濾器是基于函數(shù)回調(diào) | 基于Java的反射機(jī)制的 |
規(guī)范 | Servlet規(guī)范 | Spring規(guī)范 |
作用范圍 | 對(duì)幾乎所有的請(qǐng)求起作用 | 只對(duì)action請(qǐng)求起作用 |
除此之外,相比過濾器,攔截器能夠“看到”用戶的請(qǐng)求具體是被Spring框架的哪個(gè)控制器所處理。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- springboot過濾器和攔截器的實(shí)例代碼
- SpringBoot 過濾器、攔截器、監(jiān)聽器對(duì)比及使用場(chǎng)景分析
- SpringBoot實(shí)現(xiàn)攔截器、過濾器、監(jiān)聽器過程解析
- 詳談springboot過濾器和攔截器的實(shí)現(xiàn)及區(qū)別
- SpringBoot全局異常處理機(jī)制和配置攔截器方式
- SpringBoot實(shí)現(xiàn)全局異常處理方法總結(jié)
- SpringBoot配置GlobalExceptionHandler全局異常處理器案例
- Springboot全局異常捕獲及try catch區(qū)別解析
- Springboot?過濾器、攔截器、全局異常處理的方案處理小結(jié)
相關(guān)文章
java微信公眾號(hào)開發(fā)(搭建本地測(cè)試環(huán)境)
這篇文章主要介紹了java微信公眾號(hào)開發(fā),主要內(nèi)容有測(cè)試公眾號(hào)與本地測(cè)試環(huán)境搭建,需要的朋友可以參考下2015-12-12基于Jasypt對(duì)SpringBoot配置文件加密
這篇文章主要介紹了基于Jasypt對(duì)SpringBoot配置文件加密,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11解決feign接口返回泛型設(shè)置屬性為null的問題
這篇文章主要介紹了解決feign接口返回泛型設(shè)置屬性為null的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06SpringBoot實(shí)現(xiàn)定時(shí)任務(wù)動(dòng)態(tài)管理示例
這篇文章主要為大家介紹了SpringBoot實(shí)現(xiàn)定時(shí)任務(wù)動(dòng)態(tài)管理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Java面向?qū)ο笾畣卫O(shè)計(jì)模式詳解
這篇文章主要介紹了Java面向?qū)ο笾畣卫O(shè)計(jì)模式詳解,所謂類的單例設(shè)計(jì)模式,就是采取一定的方法保證在整個(gè)的軟件系統(tǒng)中,對(duì)某個(gè)類只能存在一個(gè)對(duì)象實(shí)例,并且該類只提供一個(gè)取得其對(duì)象實(shí)例的方法,需要的朋友可以參考下2024-01-01SpringSecurity請(qǐng)求授權(quán)規(guī)則配置與注解詳解
這篇文章主要介紹了SpringSecurity請(qǐng)求授權(quán)規(guī)則配置與注解詳解,我們常使用@Secured與@PreAuthorize兩個(gè)注解在進(jìn)入方法前進(jìn)行角色、權(quán)限的控制,進(jìn)入方法前數(shù)據(jù)的過濾@PreFilter注解偶爾會(huì)看到,需要的朋友可以參考下2023-12-12