Java實戰(zhàn):一行代碼搞定耗時性能追蹤
前言
在開發(fā)過程中,性能監(jiān)控和調(diào)試是我們經(jīng)常面對的問題。
雖然市面上有許多成熟的性能監(jiān)控工具,但有時我們需要一個輕量級、靈活且優(yōu)雅的解決方案。
當然也可以自己手動在業(yè)務(wù)代碼中進行追蹤,比如先記錄startTime,執(zhí)行結(jié)束后再拿當前時間減去startTime,計算出耗時。
但是畢竟會制造很多重復代碼。
本文將介紹如何設(shè)計和實現(xiàn)一個簡潔而優(yōu)雅的TimeTracker工具類,它不僅能滿足基本的性能追蹤需求,還支持了函數(shù)式接口、try-with-resources等多種調(diào)用機制。
最初的痛點
還記得我們是怎么記錄代碼執(zhí)行時間的嗎?到處都是這樣的代碼:
long start = System.currentTimeMillis();
try {
// 業(yè)務(wù)邏輯
} finally {
// 計算耗時
}每次都得寫這種重復又啰嗦的代碼,要不就得復制粘貼,還容易漏掉,CV大法固然好,但懶人總想要更懶的方式。
進化:擁抱 try-with-resources
偶然間,我想到了 AutoCloseable 接口,再想到每次處理流的時候,直接 try 里面一包,什么都不用關(guān)心,那是不是我也可以這樣處理執(zhí)行時間?
想象一下,如果能這樣寫,那豈不是很優(yōu)雅:
try (TimeTracker ignored = new TimeTracker("數(shù)據(jù)庫操作")) {
// 業(yè)務(wù)代碼,耗時自動搞定!
}瞬間,代碼變得清爽多了!資源自動管理,耗時自動計算,福音嘛這不是!
說干就干,新建一個 TimeTracker類,實現(xiàn) AutoCloseable,簡單鼓搗一番,重點在于,在 close() 中計算耗時,實現(xiàn)全自動化。于是就有了第一版。
當然,這才是剛開始。
Pro: 函數(shù)式接口
但是,還能更懶一點嗎?當然可以!
不妨試試函數(shù)式接口!
比如下面這樣:
TimeTracker.track("用戶查詢", () -> {
return userService.findById(123);
});連 try 都不用寫了!一行代碼搞定性能監(jiān)控,是不是很???這下點題了不是!
什么?你說這明明是3行?
那如果我這樣寫呢?
TimeTracker.track("操作", () -> riskyMethod());這下沒毛病了吧 ??
如果想要返回值,那也很簡單,直接這樣寫:
String result = TimeTracker.track("簡單任務(wù)", () -> {
Thread.sleep(1000);
return "完成";
});和普通的調(diào)用沒有區(qū)別,毫無心智負擔。
Pro Max:異常處理
雖然現(xiàn)在一行就搞定了,但是缺少一個關(guān)鍵的功能,那就是異常處理。
考量一個程序員是否????的標準,從來不是他能寫出多高大上的代碼,而且豐富的開發(fā)經(jīng)驗和強大的問題追蹤能力。
因為這里怎么能缺少異常處理。
在上面的版本中,都沒有涉及異常,因為 .track() 內(nèi)部把異常消化掉并重新包裝成了 RuntimeException。
public static <T> T track(String operationName, ThrowableSupplier<T> execution) {
try {
return trackThrows(operationName, execution);
} catch (Exception e) {
throw new RuntimeException("執(zhí)行失敗: " + operationName, e);
}
}考慮到不同場景對于異常處理的需求不同,所以還得再額外提供一種模式,允許調(diào)用方顯式地進行異常處理,把選擇權(quán)交給用戶。
比如下面這樣:
try {
TimeTracker.trackThrows("操作", () -> {
return riskyMethod(); // 保留原始異常
});
} catch (SpecificException e) {
// 精確處理
}那這樣就大功告成了。
完整代碼
下面這是完整代碼。
各種注釋都寫在里面,可以說是非常詳細了。
包括使用示例,也寫在JavaDoc里面,真正做到注釋比代碼還多。??
/**
* 性能跟蹤工具類,用于測量代碼執(zhí)行時間并提供靈活的異常處理機制。
*
* <p>主要特性:
* <ul>
* <li>精確測量代碼執(zhí)行時間</li>
* <li>支持帶返回值和無返回值的方法跟蹤</li>
* <li>提供兩種異常處理模式</li>
* <li>支持自動資源管理</li>
* </ul>
*
* <h2>使用示例:</h2>
*
* <h3> try-with-resources 手動跟蹤</h3>
* <pre>{@code
* // 手動管理資源和性能跟蹤
* try (TimeTracker tracker = new TimeTracker("數(shù)據(jù)庫操作")) {
* database.connect();
* database.executeQuery();
* } // 自動關(guān)閉,并打印執(zhí)行時間
*
* // 帶返回值的try-with-resources
* try (TimeTracker tracker = new TimeTracker("復雜計算");
* Resource resource = acquireResource()) {
* return performComplexCalculation(resource);
* }
* }</pre>
*
* <h3>結(jié)合靜態(tài)方法的try-with-resources</h3>
* <pre>{@code
* try (TimeTracker ignored = TimeTracker.of("網(wǎng)絡(luò)請求")) {
* httpClient.sendRequest();
* httpClient.receiveResponse();
* }
* }</pre>
*
* <p>注意:使用try-with-resources可以確保資源正確關(guān)閉,
* 并自動記錄執(zhí)行時間。</p>
*
* <h3>lambda自動處理異常</h3>
* <pre>{@code
* // 無返回值方法
* TimeTracker.track("數(shù)據(jù)處理", () -> {
* processData(); // 可能拋出異常的方法
* });
*
* // 有返回值方法
* String result = TimeTracker.track("查詢用戶", () -> {
* return userService.findById(123);
* });
* }</pre>
*
* <h3>lambda顯式異常處理</h3>
* <pre>{@code
* try {
* // 允許拋出原始異常
* String result = TimeTracker.trackThrows("復雜查詢", () -> {
* return complexQuery(); // 可能拋出檢查異常
* });
* } catch (SQLException e) {
* // 精確處理特定異常
* logger.error("數(shù)據(jù)庫查詢失敗", e);
* }
* }</pre>
*
* <h3>lambda嵌套使用</h3>
* <pre>{@code
* TimeTracker.track("整體流程", () -> {
* // 子任務(wù)1
* TimeTracker.track("數(shù)據(jù)準備", () -> prepareData());
*
* // 子任務(wù)2
* return TimeTracker.track("數(shù)據(jù)處理", () -> processData());
* });
* }</pre>
*
* <p>注意:默認情況下會打印執(zhí)行時間到控制臺。對于生產(chǎn)環(huán)境,
* 建議根據(jù)需要自定義日志記錄機制。</p>
*
* @author [Your Name]
* @version 1.0
* @since [版本號]
*/
public class TimeTracker implements AutoCloseable {
/** 操作名稱 */
private final String operationName;
/** 開始時間(納秒) */
private final long startTime;
/** 是否啟用日志 */
private final boolean logEnabled;
/**
* 創(chuàng)建一個新的TimeTracker實例。
*
* @param operationName 要跟蹤的操作名稱
*/
public TimeTracker(String operationName) {
this(operationName, true);
}
/**
* 私有構(gòu)造函數(shù),用于創(chuàng)建TimeTracker實例。
*
* @param operationName 操作名稱
* @param logEnabled 是否啟用日志輸出
*/
private TimeTracker(String operationName, boolean logEnabled) {
this.operationName = operationName;
this.startTime = System.nanoTime();
this.logEnabled = logEnabled;
if (logEnabled) {
System.out.printf("開始執(zhí)行: %s%n", operationName);
}
}
/**
* 創(chuàng)建一個新的TimeTracker實例的靜態(tài)工廠方法。
*
* @param operationName 要跟蹤的操作名稱
* @return 新的TimeTracker實例
*/
public static TimeTracker of(String operationName) {
return new TimeTracker(operationName);
}
/**
* 跟蹤帶返回值的代碼塊執(zhí)行時間,異常會被包裝為RuntimeException。
*
* @param operationName 操作名稱
* @param execution 要執(zhí)行的代碼塊
* @param <T> 返回值類型
* @return 代碼塊的執(zhí)行結(jié)果
* @throws RuntimeException 如果執(zhí)行過程中發(fā)生異常
*/
public static <T> T track(String operationName, ThrowableSupplier<T> execution) {
try {
return trackThrows(operationName, execution);
} catch (Exception e) {
throw new RuntimeException("執(zhí)行失敗: " + operationName, e);
}
}
/**
* 跟蹤帶返回值的代碼塊執(zhí)行時間,允許拋出異常。
*
* @param operationName 操作名稱
* @param execution 要執(zhí)行的代碼塊
* @param <T> 返回值類型
* @return 代碼塊的執(zhí)行結(jié)果
* @throws Exception 如果執(zhí)行過程中發(fā)生異常
*/
public static <T> T trackThrows(String operationName, ThrowableSupplier<T> execution) throws Exception {
try (TimeTracker ignored = new TimeTracker(operationName, true)) {
return execution.get();
}
}
/**
* 跟蹤無返回值的代碼塊執(zhí)行時間,異常會被包裝為RuntimeException。
*
* @param operationName 操作名稱
* @param execution 要執(zhí)行的代碼塊
* @throws RuntimeException 如果執(zhí)行過程中發(fā)生異常
*/
public static void track(String operationName, ThrowableRunnable execution) {
try {
trackThrows(operationName, execution);
} catch (Exception e) {
throw new RuntimeException("執(zhí)行失敗: " + operationName, e);
}
}
/**
* 跟蹤無返回值的代碼塊執(zhí)行時間,允許拋出異常。
*
* @param operationName 操作名稱
* @param execution 要執(zhí)行的代碼塊
* @throws Exception 如果執(zhí)行過程中發(fā)生異常
*/
public static void trackThrows(String operationName, ThrowableRunnable execution) throws Exception {
try (TimeTracker ignored = new TimeTracker(operationName, true)) {
execution.run();
}
}
@Override
public void close() {
if (logEnabled) {
// 計算執(zhí)行時間(轉(zhuǎn)換為毫秒)
long timeElapsed = (System.nanoTime() - startTime) / 1_000_000;
System.out.printf("%s 執(zhí)行完成,耗時: %d ms%n", operationName, timeElapsed);
}
}
/**
* 可拋出異常的Supplier函數(shù)式接口。
*
* @param <T> 返回值類型
*/
@FunctionalInterface
public interface ThrowableSupplier<T> {
/**
* 獲取結(jié)果。
*
* @return 執(zhí)行結(jié)果
* @throws Exception 如果執(zhí)行過程中發(fā)生錯誤
*/
T get() throws Exception;
}
/**
* 可拋出異常的Runnable函數(shù)式接口。
*/
@FunctionalInterface
public interface ThrowableRunnable {
/**
* 執(zhí)行操作。
*
* @throws Exception 如果執(zhí)行過程中發(fā)生錯誤
*/
void run() throws Exception;
}
}一個DEMO
在JavaDoc里面已經(jīng)清楚寫明了調(diào)用示例,這里額外再補充一個Demo類,可能更清晰
import java.io.IOException;
public class TimeTrackerDemo {
public void demonstrateUsage() {
// 1. 使用不拋出檢查異常的版本(異常被包裝為RuntimeException)
TimeTracker.track("簡單任務(wù)", () -> {
Thread.sleep(1000);
return "完成";
});
// 2. 使用可能拋出異常的版本
try {
TimeTracker.trackThrows("可能失敗的任務(wù)", () -> {
if (Math.random() < 0.5) {
throw new IOException("模擬IO異常");
}
return "成功";
});
} catch (Exception e) {
// 處理異常
e.printStackTrace();
}
// 3. 嵌套使用示例
try {
TimeTracker.trackThrows("復雜流程", () -> {
// 子任務(wù)1:使用不拋出異常的版本
TimeTracker.track("子任務(wù)1", () -> {
Thread.sleep(500);
});
// 子任務(wù)2:使用拋出異常的版本
return TimeTracker.trackThrows("子任務(wù)2", () -> {
Thread.sleep(500);
return "全部完成";
});
});
} catch (Exception e) {
// 處理異常
e.printStackTrace();
}
// 4. try-with-resources 示例
try (TimeTracker tracker = TimeTracker.of("資源管理演示")) {
// 模擬資源操作
performResourceIntensiveTask();
}
// 5. 多資源管理的try-with-resources
try (
TimeTracker tracker1 = TimeTracker.of("第一階段");
TimeTracker tracker2 = TimeTracker.of("第二階段");
// 可以同時管理其他資源
CustomResource resource = acquireResource()
) {
processResourcesSequentially(resource);
} catch (Exception e) {
// 異常處理
e.printStackTrace();
}
// 6. 忽略返回值的try-with-resources
try (TimeTracker ignored = TimeTracker.of("后臺任務(wù)")) {
performBackgroundTask();
}
}
// 輔助方法(僅作示例)
private void performResourceIntensiveTask() {
Thread.sleep(1000);
System.out.println("資源密集型任務(wù)完成");
}
private CustomResource acquireResource() {
return new CustomResource();
}
private void processResourcesSequentially(CustomResource resource) {
// 處理資源的示例方法
resource.process();
}
private void performBackgroundTask() {
// 后臺任務(wù)示例
System.out.println("執(zhí)行后臺任務(wù)");
}
// 模擬自定義資源類
private static class CustomResource implements AutoCloseable {
public void process() {
System.out.println("處理資源");
}
@Override
public void close() {
System.out.println("關(guān)閉資源");
}
}
}改進建議
當然,這個類還有很大的改進空間,我簡單列幾個,列位看官可以根據(jù)自己的真實場景再逐步進行優(yōu)化。
- 集成日志框架,比如Slf4j,支持更靈活的輸出方式
- 添加更多的時間統(tǒng)計維度(最大值、最小值、平均值等)
- 添加性能指標收集,支持監(jiān)控數(shù)據(jù)統(tǒng)計
- 支持異步操作
革命尚未成功,同志仍需努力。
總結(jié)
一點點經(jīng)驗
先來點經(jīng)驗總結(jié),仁者見仁,智者見智。
- 工具類設(shè)計務(wù)必要注重實用性和易用性的平衡
- 工具類只是工具,千萬不能在工具類中牽扯業(yè)務(wù)
- 異常處理需要考慮實際的真實的使用場景
- 合理使用語言特性,可以大大簡化代碼
- 魯棒性非常重要
寫在最后
寫代碼這些年,常常要記錄些執(zhí)行時間。起初也是簡單,System.currentTimeMillis() 放在前后,相減便知道耗了多少毫秒。后來覺得這樣寫著繁瑣,且容易忘記處理異常,索性就做了這么個工具類。
說來也沒什么新奇的,不過是用了Java里的AutoCloseable接口,再配上lambda表達式,讓代碼看起來干凈些。倒是在處理異常時費了點心思,畢竟實際開發(fā)中,異常處理往往比主要邏輯還要來得復雜。
回頭再看這段代碼,倒也不覺得有多少技術(shù)含量,但確實解決了實際問題。這大概就是寫程序的意思:不是為了寫出多么驚世駭俗的代碼,而是讓原本繁瑣的事情變得簡單,讓使用者覺得舒服。
就像一把稱手的菜刀,好就好在切起菜來只覺得順手,從不會讓人去想它多么多么精妙。這個工具類也是這樣,它就在那里,不聲不響地做著它的事情。































