1. 程式人生 > >記錄請求的耗時(攔截器、過濾器、aspect)

記錄請求的耗時(攔截器、過濾器、aspect)

color 字段 方法 wired 創建 prehandle throwable exce pointcut

文章前言

記錄控制器請求的耗時處理通常有三種實現方式,分別是:過濾器、攔截器、aspect;下文將逐一實現。

1、Filter 過濾器

1.1、方法說明

需要實現 Filter 類,主要涉及三個方法:
  1. destory:銷毀
  2. doFilter:處理過濾器邏輯
  3. 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 類,主要涉及三個方法:
  1. preHandle:請求方法之前被調用;
  2. postHandle:控制器處理方法之後會被調用,前提是沒有拋出異常,拋出異常不會調用;
  3. 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)