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


文章前言

記錄控制器請求的耗時處理通常有三種實現方式,分別是:過濾器、攔截器、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
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM