記錄請求的耗時(攔截器、過濾器、aspect)
阿新 • • 發佈:2018-12-22
color 字段 方法 wired 創建 prehandle throwable exce pointcut
文章前言
記錄控制器請求的耗時處理通常有三種實現方式,分別是:過濾器、攔截器、aspect;下文將逐一實現。
1、Filter 過濾器
1.1、方法說明
需要實現 Filter 類,主要涉及三個方法:- destory:銷毀
- doFilter:處理過濾器邏輯
- init:filter 初始化時調用
1.2、代碼部分
@Component //表明作為spring的一個bean public class TimeFilter implements Filter { @Override public void destroy() { System.out.println("time filter destroy"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("time filter start"); long start = new Date().getTime();//過濾器主要邏輯,整個處理流程 chain.doFilter(request, response); System.out.println("time filter 耗時:"+ (new Date().getTime() - start)); System.out.println("time filter finish"); } @Override public void init(FilterConfig arg0) throws ServletException { System.out.println("time filter init"); } }
1.3、補充說明
通過過濾器攔截請求的方式,有一個問題,只能拿到 http 的請求和響應(request、response),意味著只能在請求或者響應裏獲取一些參數; 但是當前請求到底是哪個控制器的哪個方法處理的,在filter裏是無法獲取的,因為實現的 Filter 這個接口是由javax(j2e)定義的, 而我們要攔截的控制器(Controller)中的請求是springmvc定義的,所以 Filter 是無法獲取 spring 相關信息。 帶出下一個主角,Interceptor 攔截器,springmvc 提供。2、Interceptor 攔截器
2.1、方法說明
需要實現 HandlerInterceptor 類,主要涉及三個方法:- preHandle:請求方法之前被調用;
- postHandle:控制器處理方法之後會被調用,前提是沒有拋出異常,拋出異常不會調用;
- afterCompletion:請求方法之後被調用,該方法總會被調用,如果出現了異常,將被封裝到Exception對象中。
2.2、代碼部分
@Component public class TimeInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle"); System.out.println(((HandlerMethod)handler).getBean().getClass().getName()); System.out.println(((HandlerMethod)handler).getMethod().getName()); request.setAttribute("startTime", new Date().getTime()); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle"); Long start = (Long) request.getAttribute("startTime"); System.out.println("time interceptor 耗時:"+ (new Date().getTime() - start)); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion"); Long start = (Long) request.getAttribute("startTime"); System.out.println("time interceptor 耗時:"+ (new Date().getTime() - start)); System.out.println("ex is "+ex); } }
2.3、補充部分
interceptor 其實等價於將 filter doFilter() 方法中的 chain.doFilter(request, response) 分成兩部分:preHandle() 、 postHandle() interceptor比 filter 的優勢在於方法上多了另外一個參數,Object handler 該參數是用來處理當前 request 請求的控制器方法的聲明; 意味著,你可以通過該參數獲取當前控制器相關的信息,如控制器名稱、請求的方法名。2.4、註意部分
interceptor 跟 Filter 不一樣,光聲明一個 @Component 是無法達到攔截器起作用,還需要一些額外的配置。@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @SuppressWarnings("unused") @Autowired private TimeInterceptor timeInterceptor; // 攔截器的一個註冊器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(timeInterceptor); } }這個配置類需要繼承 WebMvcConfigurerAdapter,並重寫添加攔截器的方法 addInterceptors,將自定義攔截器添加到應用中。 這時候攔截器就生效了。
2.5、繼續補充
攔截器會攔截所有控制器裏的方法調用。但是卻有一個缺陷,其無法獲取前端訪問方法的時候所攜帶的參數的。 為什麽會這麽說? 從Spring MVC 的 DispatcherServlet 的源代碼中可以發現,找到 doDispatch() 方法,也就是請求分發的方法,有一段代碼如下: 如果我們自定的 Interceptor 的 preHandler 方法返回的是 false,分發任務就會截止,不再繼續執行下面的代碼, 而下面的一行代碼正是將前端攜帶的參數進行映射的邏輯,也就是說,preHandler 方法不會接觸到前端攜帶來的參數,也就是說攔截器無法處理參數。 所以這裏引進 AOP 進行攔截。3、Aspect
描述AOP常用的一些術語有:通知(Adivce)、連接點(Join point)、切點(Pointcut)、切面(Aspect)、引入(Introduction)、織入(Weaving) 首先明確的核心概念:切面 = 切點 + 通知。3.1、通知(Adivce)
通知分為五種類型: Before(前置通知):在目標方法被調用之前調用通知功能 After(後置通知):在目標方法完成後調用通知,無論方法是否執行成功,不會關心方法的輸出是什麽 After-returning(返回通知):在目標方法成功執行之後調用通知 After-throwing(異常通知):在目標方法拋出異常後調用通知 Around(通知環繞):通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行為
3.2、連接點(Join point)
連接點是一個應用執行過程中能夠插入一個切面的點。 比如:方法調用、方法執行、字段設置/獲取、異常處理執行、類初始化、甚至是for循環中的某個點。 理論上, 程序執行過程中的任何時點都可以作為作為織入點, 而所有這些執行時點都是Joint point, 但 Spring AOP 目前僅支持方法執行 (method execution)。
3.3、切點(Pointcut)
通知(advice)定義了切面何時,那麽切點就是定義切面“何處” 描述某一類 Joint points,
比如定義了很多 Joint point, 對於 Spring AOP 來說就是匹配哪些方法的執行。
3.4、切面(Aspect)
切面是切點和通知的結合。通知和切點共同定義了關於切面的全部內容 —— 它是什麽時候,在何時和何處完成功能。
3.5、引入(Introduction)
引用允許我們向現有的類添加新的方法或者屬性
3.6、織入(Weaving)
組裝方面來創建一個被通知對象。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在運行時完成。
Spring和其他純Java AOP框架一樣,在運行時完成織入。
來看一下 Aspect 怎麽寫:
@Aspect @Component public class TimeAspect { @Around("execution(* club.sscai.security.web.controller.UserController.*(..))") public Object handleTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("time aspect is start."); for (Object object : proceedingJoinPoint.getArgs()) { System.out.println(object); } long startTime = System.currentTimeMillis(); Object obj = proceedingJoinPoint.proceed(); System.out.println("time aspect 耗時:" + (System.currentTimeMillis() - startTime)); System.out.println("time aspect finish."); return obj; } }@Around 定義了環繞通知,也就是定義了何時使用切面,表達式"execution(* club.sscai.security.web.controller.UserController.*(..))"定義了再哪裏使用。 ProceedingJoinPoint 對象的 proceed() 方法表示執行被攔截的方法,它有一個 Object 類型的返回值,是原有方法的返回值,後期使用的時候往往需要強轉。 對於上面三種攔截方式,他們的執行有一個基本的順序,進入的順序是: Filter-->Interceptor-->Aspect-->Controller-->Aspect-->Interceptor-->Filter(不考慮異常的發生)。 如下所示:
博客地址:http://www.cnblogs.com/niceyoo
記錄請求的耗時(攔截器、過濾器、aspect)