在咱們的實際開發中,多多少少會遇到統計一段代碼片斷的耗時的狀況,咱們通常的寫法以下git
long start = System.currentTimeMillis(); try { // .... 具體的代碼段 } finally { System.out.println("cost: " + (System.currentTimeMillis() - start)); }
上面的寫法沒有什麼毛病,可是看起來就不太美觀了,那麼有沒有什麼更優雅的寫法呢?github
<!-- more -->spring
瞭解 Spring AOP 的同窗可能立馬會想到一個解決方法,若是想要統計某個方法耗時,使用切面能夠無侵入的實現,如ide
// 定義切點,攔截全部知足條件的方法 @Pointcut("execution(public * com.git.hui.boot.aop.demo.*.*(*))") public void point() { } @Around("point()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); try{ return joinPoint.proceed(); } finally { System.out.println("cost: " + (System.currentTimeMillis() - start)); } }
Spring AOP 的底層支持原理爲代理模式,爲目標對象提供加強功能;在 Spring 的生態體系下,使用 aop 的方式來統計方法耗時,能夠說少侵入且實現簡單,可是有如下幾個問題學習
在 JDK1.7 引入了一個新的接口AutoCloseable
, 一般它的實現類配合try{}
使用,可在 IO 流的使用上,常常能夠看到下面這種寫法測試
// 讀取文件內容並輸出 try (Reader stream = new BufferedReader(new InputStreamReader(new FileInputStream("/tmp")))) { List<String> list = ((BufferedReader) stream).lines().collect(Collectors.toList()); System.out.println(list); } catch (IOException e) { e.printStackTrace(); }
注意上面的寫法中,最值得關注一點是,不須要再主動的寫stream.close
了,主要緣由就是在try(){}
執行完畢以後,會調用方法AutoCloseable#close
方法;ui
基於此,咱們就會有一個大單的想法,下一個Cost
類實現AutoCloseable
接口,建立時記錄一個時間,close 方法中記錄一個時間,並輸出時間差值;將須要統計耗時的邏輯放入try(){}
代碼塊this
下面是一個具體的實現:代理
public static class Cost implements AutoCloseable { private long start; public Cost() { this.start = System.currentTimeMillis(); } @Override public void close() { System.out.println("cost: " + (System.currentTimeMillis() - start)); } } public static void testPrint() { for (int i = 0; i < 5; i++) { System.out.println("now " + i); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { try (Cost c = new Cost()) { testPrint(); } System.out.println("------over-------"); }
執行後輸出以下:
now 0 now 1 now 2 now 3 now 4 cost: 55 ------over-------
若是代碼塊拋異常,也會正常輸出耗時麼?
public static void testPrint() { for (int i = 0; i < 5; i++) { System.out.println("now " + i); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (i == 3) { throw new RuntimeException("some exception!"); } } }
再次輸出以下,並無問題
now 0 now 1 now 2 now 3 cost: 46 Exception in thread "main" java.lang.RuntimeException: some exception! at com.git.hui.boot.order.Application.testPrint(Application.java:43) at com.git.hui.boot.order.Application.main(Application.java:50)
除了上面介紹的兩種方式,還有一種在業務開發中不太常見,可是在中間件、偏基礎服務的功能組件中能夠看到,利用 Java Agent 探針技術來實現,好比阿里的 arthas 就是在 JavaAgent 的基礎上作了各類上天的功能,後續介紹 java 探針技術時會專門介紹
下面小結一下三種統計耗時的方式
基本寫法
long start = System.currentTimeMillis(); try { // .... 具體的代碼段 } finally { System.out.println("cost: " + (System.currentTimeMillis() - start)); }
優勢是簡單,適用範圍普遍;缺點是侵入性強,大量的重複代碼
Spring AOP
在 Spring 生態下,能夠藉助 AOP 來攔截目標方法,統計耗時
@Around("...") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); try{ return joinPoint.proceed(); } finally { System.out.println("cost: " + (System.currentTimeMillis() - start)); } }
優勢:無侵入,適合統一管理(好比測試環境輸出統計耗時,生產環境不輸出);缺點是適用範圍小,且粒度爲方法級別,並受限於 AOP 的使用範圍
AutoCloseable
這種方式能夠看作是第一種寫法的進階版
// 定義類 public static class Cost implements AutoCloseable { private long start; public Cost() { this.start = System.currentTimeMillis(); } @Override public void close() { System.out.println("cost: " + (System.currentTimeMillis() - start)); } } // 使用姿式 try (Cost c = new Cost()) { ... }
優勢是:簡單,適用範圍普遍,且適合統一管理;缺點是依然有代碼侵入
說明
上面第二種方法看着屬於最優雅的方式,可是限制性強;若是有更靈活的需求,建議考慮第三種寫法,在代碼的簡潔性和統一管理上都要優雅不少,相比較第一種能夠減小大量冗餘代碼
一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛
盡信書則不如,已上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
一灰灰 blog