⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7436784712028815371 「一只叫煤球的猫」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

前言

在开发过程中,性能监控和调试是我们经常面对的问题。

虽然市面上有许多成熟的性能监控工具,但有时我们需要一个轻量级、灵活且优雅的解决方案。

当然也可以自己手动在业务代码中进行追踪,比如先记录startTime,执行结束后再拿当前时间减去startTime,计算出耗时。但是毕竟会制造很多重复代码。

本文将介绍如何设计和实现一个简洁而优雅的TimeTracker工具类,它不仅能满足基本的性能追踪需求,还支持了函数式接口、try-with-resources等多种调用机制。

正文

最初的痛点

还记得我们是怎么记录代码执行时间的吗?到处都是这样的代码:

long start = System.currentTimeMillis();
try {
// 业务逻辑
} finally {
// 计算耗时
}

每次都得写这种重复又啰嗦的代码,要不就得复制粘贴,还容易漏掉,CV大法固然好,但懒人总想要更懒的方式。

进化:拥抱 try-with-resources

偶然间,我想到了 AutoCloseable 接口,再想到每次处理流的时候,直接 try 里面一包,什么都不用关心,那是不是我也可以这样处理执行时间?

想象一下,如果能这样写,那岂不是很优雅:

try (TimeTracker ignored = new TimeTracker("数据库操作")) {
// 业务代码,耗时自动搞定!
}

瞬间,代码变得清爽多了!资源自动管理,耗时自动计算,福音嘛这不是!

说干就干,新建一个 TimeTracker类,实现 AutoCloseable,简单鼓捣一番,重点在于,在 close() 中计算耗时,实现全自动化。于是就有了第一版。

当然,这才是刚开始。

Pro: 函数式接口

但是,还能更懒一点吗?当然可以!

不妨试试函数式接口!

比如下面这样:

TimeTracker.track("用户查询", () -> {
return userService.findById(123);
});

连 try 都不用写了!一行代码搞定性能监控,是不是很牛?这下点题了不是!

什么?你说这明明是3行?

那如果我这样写呢?

TimeTracker.track("操作", () -> riskyMethod());

这下没毛病了吧 😂

如果想要返回值,那也很简单,直接这样写:

String result = TimeTracker.track("简单任务", () -> {
Thread.sleep(1000);
return "完成";
});

和普通的调用没有区别,毫无心智负担。

Pro Max:异常处理

虽然现在一行就搞定了,但是缺少一个关键的功能,那就是异常处理。

考量一个程序员是否🐂🍺的标准,从来不是他能写出多高大上的代码,而且丰富的开发经验和强大的问题追踪能力。 因为这里怎么能缺少异常处理。

在上面的版本中,都没有涉及异常,因为 .track() 内部把异常消化掉并重新包装成了 RuntimeException。

public static <T> T track(String operationName, ThrowableSupplier<T> execution) {
try {
return trackThrows(operationName, execution);
} catch (Exception e) {
throw new RuntimeException("执行失败: " + operationName, e);
}
}

考虑到不同场景对于异常处理的需求不同,所以还得再额外提供一种模式,允许调用方显式地进行异常处理,把选择权交给用户。

比如下面这样:

try {
TimeTracker.trackThrows("操作", () -> {
return riskyMethod(); // 保留原始异常
});
} catch (SpecificException e) {
// 精确处理
}

那这样就大功告成了。

完整代码

下面这是完整代码。

各种注释都写在里面,可以说是非常详细了。

包括使用示例,也写在JavaDoc里面,真正做到注释比代码还多。😁

/**
* 性能跟踪工具类,用于测量代码执行时间并提供灵活的异常处理机制。
*
* <p>主要特性:
* <ul>
* <li>精确测量代码执行时间</li>
* <li>支持带返回值和无返回值的方法跟踪</li>
* <li>提供两种异常处理模式</li>
* <li>支持自动资源管理</li>
* </ul>
*
* <h2>使用示例:</h2>
*
* <h3> try-with-resources 手动跟踪</h3>
* <pre>{@code
* // 手动管理资源和性能跟踪
* try (TimeTracker tracker = new TimeTracker("数据库操作")) {
* database.connect();
* database.executeQuery();
* } // 自动关闭,并打印执行时间
*
* // 带返回值的try-with-resources
* try (TimeTracker tracker = new TimeTracker("复杂计算");
* Resource resource = acquireResource()) {
* return performComplexCalculation(resource);
* }
* }</pre>
*
* <h3>结合静态方法的try-with-resources</h3>
* <pre>{@code
* try (TimeTracker ignored = TimeTracker.of("网络请求")) {
* httpClient.sendRequest();
* httpClient.receiveResponse();
* }
* }</pre>
*
* <p>注意:使用try-with-resources可以确保资源正确关闭,
* 并自动记录执行时间。</p>
*
* <h3>lambda自动处理异常</h3>
* <pre>{@code
* // 无返回值方法
* TimeTracker.track("数据处理", () -> {
* 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("数据库查询失败", e);
* }
* }</pre>
*
* <h3>lambda嵌套使用</h3>
* <pre>{@code
* TimeTracker.track("整体流程", () -> {
* // 子任务1
* TimeTracker.track("数据准备", () -> prepareData());
*
* // 子任务2
* return TimeTracker.track("数据处理", () -> processData());
* });
* }</pre>
*
* <p>注意:默认情况下会打印执行时间到控制台。对于生产环境,
* 建议根据需要自定义日志记录机制。</p>
*
* @author [Your Name]
* @version 1.0
* @since [版本号]
*/
publicclass TimeTracker implements AutoCloseable {
/** 操作名称 */
privatefinal String operationName;
/** 开始时间(纳秒) */
privatefinallong startTime;
/** 是否启用日志 */
privatefinalboolean logEnabled;

/**
* 创建一个新的TimeTracker实例。
*
* @param operationName 要跟踪的操作名称
*/
public TimeTracker(String operationName) {
this(operationName, true);
}

/**
* 私有构造函数,用于创建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("开始执行: %s%n", operationName);
}
}

/**
* 创建一个新的TimeTracker实例的静态工厂方法。
*
* @param operationName 要跟踪的操作名称
* @return 新的TimeTracker实例
*/
public static TimeTracker of(String operationName) {
returnnew TimeTracker(operationName);
}

/**
* 跟踪带返回值的代码块执行时间,异常会被包装为RuntimeException。
*
* @param operationName 操作名称
* @param execution 要执行的代码块
* @param <T> 返回值类型
* @return 代码块的执行结果
* @throws RuntimeException 如果执行过程中发生异常
*/
publicstatic <T> T track(String operationName, ThrowableSupplier<T> execution) {
try {
return trackThrows(operationName, execution);
} catch (Exception e) {
thrownew RuntimeException("执行失败: " + operationName, e);
}
}

/**
* 跟踪带返回值的代码块执行时间,允许抛出异常。
*
* @param operationName 操作名称
* @param execution 要执行的代码块
* @param <T> 返回值类型
* @return 代码块的执行结果
* @throws Exception 如果执行过程中发生异常
*/
publicstatic <T> T trackThrows(String operationName, ThrowableSupplier<T> execution) throws Exception {
try (TimeTracker ignored = new TimeTracker(operationName, true)) {
return execution.get();
}
}

/**
* 跟踪无返回值的代码块执行时间,异常会被包装为RuntimeException。
*
* @param operationName 操作名称
* @param execution 要执行的代码块
* @throws RuntimeException 如果执行过程中发生异常
*/
public static void track(String operationName, ThrowableRunnable execution) {
try {
trackThrows(operationName, execution);
} catch (Exception e) {
thrownew RuntimeException("执行失败: " + operationName, e);
}
}

/**
* 跟踪无返回值的代码块执行时间,允许抛出异常。
*
* @param operationName 操作名称
* @param execution 要执行的代码块
* @throws Exception 如果执行过程中发生异常
*/
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) {
// 计算执行时间(转换为毫秒)
long timeElapsed = (System.nanoTime() - startTime) / 1_000_000;
System.out.printf("%s 执行完成,耗时: %d ms%n", operationName, timeElapsed);
}
}

/**
* 可抛出异常的Supplier函数式接口。
*
* @param <T> 返回值类型
*/
@FunctionalInterface
publicinterface ThrowableSupplier<T> {
/**
* 获取结果。
*
* @return 执行结果
* @throws Exception 如果执行过程中发生错误
*/
T get() throws Exception;
}

/**
* 可抛出异常的Runnable函数式接口。
*/
@FunctionalInterface
publicinterface ThrowableRunnable {
/**
* 执行操作。
*
* @throws Exception 如果执行过程中发生错误
*/
void run() throws Exception;
}
}

一个DEMO

在JavaDoc里面已经清楚写明了调用示例,这里额外再补充一个Demo类,可能更清晰

import java.io.IOException;

publicclass TimeTrackerDemo {

public void demonstrateUsage() {
// 1. 使用不抛出检查异常的版本(异常被包装为RuntimeException)
TimeTracker.track("简单任务", () -> {
Thread.sleep(1000);
return"完成";
});

// 2. 使用可能抛出异常的版本
try {
TimeTracker.trackThrows("可能失败的任务", () -> {
if (Math.random() < 0.5) {
thrownew IOException("模拟IO异常");
}
return"成功";
});
} catch (Exception e) {
// 处理异常
e.printStackTrace();
}

// 3. 嵌套使用示例
try {
TimeTracker.trackThrows("复杂流程", () -> {
// 子任务1:使用不抛出异常的版本
TimeTracker.track("子任务1", () -> {
Thread.sleep(500);
});

// 子任务2:使用抛出异常的版本
return TimeTracker.trackThrows("子任务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("后台任务")) {
performBackgroundTask();
}
}

// 辅助方法(仅作示例)
private void performResourceIntensiveTask() {
Thread.sleep(1000);
System.out.println("资源密集型任务完成");
}

private CustomResource acquireResource() {
returnnew CustomResource();
}

private void processResourcesSequentially(CustomResource resource) {
// 处理资源的示例方法
resource.process();
}

private void performBackgroundTask() {
// 后台任务示例
System.out.println("执行后台任务");
}

// 模拟自定义资源类
privatestaticclass CustomResource implements AutoCloseable {
public void process() {
System.out.println("处理资源");
}

@Override
public void close() {
System.out.println("关闭资源");
}
}
}

改进建议

当然,这个类还有很大的改进空间,我简单列几个,列位看官可以根据自己的真实场景再逐步进行优化。

  • 集成日志框架,比如Slf4j,支持更灵活的输出方式
  • 添加更多的时间统计维度(最大值、最小值、平均值等)
  • 添加性能指标收集,支持监控数据统计
  • 支持异步操作

革命尚未成功,同志仍需努力。

总结

一点点经验

先来点经验总结,仁者见仁,智者见智。

  • 工具类设计务必要注重实用性和易用性的平衡
  • 工具类只是工具,千万不能在工具类中牵扯业务
  • 异常处理需要考虑实际的真实的使用场景
  • 合理使用语言特性,可以大大简化代码
  • 鲁棒性非常重要

写在最后

写代码这些年,常常要记录些执行时间。起初也是简单,System.currentTimeMillis() 放在前后,相减便知道耗了多少毫秒。后来觉得这样写着繁琐,且容易忘记处理异常,索性就做了这么个工具类。

说来也没什么新奇的,不过是用了Java里的AutoCloseable接口,再配上lambda表达式,让代码看起来干净些。倒是在处理异常时费了点心思,毕竟实际开发中,异常处理往往比主要逻辑还要来得复杂。

回头再看这段代码,倒也不觉得有多少技术含量,但确实解决了实际问题。这大概就是写程序的意思:不是为了写出多么惊世骇俗的代码,而是让原本繁琐的事情变得简单,让使用者觉得舒服。

就像一把称手的菜刀,好就好在切起菜来只觉得顺手,从不会让人去想它多么多么精妙。这个工具类也是这样,它就在那里,不声不响地做着它的事情。

总说要写优雅的代码,但其实代码写到最后,都是平常的。

就像一碗白米饭,好就好在它的本分。

文章目录
  1. 1. 前言
  2. 2. 正文
    1. 2.1. 最初的痛点
    2. 2.2. 进化:拥抱 try-with-resources
    3. 2.3. Pro: 函数式接口
    4. 2.4. Pro Max:异常处理
    5. 2.5. 完整代码
    6. 2.6. 一个DEMO
    7. 2.7. 改进建议
  3. 3. 总结
    1. 3.0.1. 一点点经验
    2. 3.0.2. 写在最后