前言
在日常开发中,经常会遇到一些性能问题。
比如用户反馈:"这个页面加载好慢啊!" 这个时候,你该怎么办?
首先就得找出到底是哪个方法、哪段代码执行时间过长。
只有找到了瓶颈,才能对症下药进行优化。所以说,方法耗时统计是性能优化中非常重要的一环。
接下来,我就给大家介绍七种实用的实现方式,从简单到复杂,总有一种适合你!
1. System.currentTimeMillis()
这是最原始但最直接的方式,适用于快速验证某段代码的执行时间。
java
public void doSomething() {
long start = System.currentTimeMillis();
// 模拟业务逻辑
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
long end = System.currentTimeMillis();
System.out.println("方法执行耗时:" + (end - start) + "ms");
}
优点
- 无需引入任何依赖
- 简单直观,适合临时调试
缺点
- 代码侵入性强
- 多处使用时重复代码多
- 精度受系统时钟影响(可能受NTP调整干扰)
适用场景
- 本地开发调试
- 快速验证某段逻辑耗时
⚠️ 注意:该方法基于系统时间,不适用于高精度计时。推荐使用
System.nanoTime()
替代(见后文补充)。
2. 使用StopWatch
工具类
Spring 提供了org.springframework.util.StopWatch
类,支持分段计时和格式化输出,适合需要统计多个子任务耗时的场景。
java
import org.springframework.util.StopWatch;
public void processUserFlow() {
StopWatch stopWatch = new StopWatch("用户处理流程");
stopWatch.start("查询用户");
// 查询逻辑...
Thread.sleep(50);
stopWatch.stop();
stopWatch.start("更新缓存");
// 缓存操作...
Thread.sleep(80);
stopWatch.stop();
log.info(stopWatch.prettyPrint());
}
输出示例:
markdown
StopWatch '用户处理流程': running time = 130897800 ns
-----------------------------------------
ms % Task name
-----------------------------------------
50.00 38% 查询用户
80.00 62% 更新缓存
优点
- 支持多任务分段计时
- 输出美观,便于分析
- 可命名任务,提升可读性
缺点
- 仍需手动插入代码
- 不适用于自动化监控
适用场景
- 需要分析多个步骤耗时占比的复杂流程
3. 使用AOP
切面+自定义注解(推荐)
通过面向切面编程(AOP),可以实现对指定方法的无侵入式耗时监控。
第一步:定义注解
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogCostTime {
String value() default ""; // 方法描述
long threshold() default 0; // 耗时阈值(ms),超过则告警
}
第二步:编写切面
java
@Aspect
@Component
@Slf4j
@Order(1) // 确保优先级
public class CostTimeAspect {
@Around("@annotation(logCostTime)")
public Object around(ProceedingJoinPoint pjp, LogCostTime logCostTime) throws Throwable {
String methodName = pjp.getSignature().getName();
String desc = logCostTime.value();
long threshold = logCostTime.threshold();
long start = System.nanoTime(); // 高精度计时
Object result;
try {
result = pjp.proceed();
} finally {
long costNanos = System.nanoTime() - start;
long costMillis = TimeUnit.NANOSECONDS.toMillis(costNanos);
// 根据阈值决定日志级别
if (threshold > 0 && costMillis > threshold) {
log.warn("方法: {}.{}({}) 耗时超阈值: {} ms (阈值: {} ms)",
pjp.getTarget().getClass().getSimpleName(), methodName, desc, costMillis, threshold);
} else {
log.info("方法: {}.{}({}) 耗时: {} ms",
pjp.getTarget().getClass().getSimpleName(), methodName, desc, costMillis);
}
}
return result;
}
}
注意:需确保项目已启用 AOP,Spring Boot 默认支持;否则需添加
@EnableAspectJAutoProxy
。
第三步:使用注解
java
@Service
public class UserService {
@LogCostTime(value = "根据ID查询用户", threshold = 50)
public User getUserById(Long id) {
try {
Thread.sleep(100); // 模拟耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return userRepository.findById(id);
}
}
输出:
css
WARN ... 方法: UserService.getUserById(根据ID查询用户) 耗时超阈值: 102 ms (阈值: 50 ms)
优点
- 低侵入:只需添加注解
- 可复用:一处定义,多处使用
- 可扩展:支持阈值告警、慢查询监控等
适用场景
- 核心服务方法
- 远程调用(RPC/HTTP)
- 数据库查询
- 复杂计算逻辑
4. 使用Micrometer @Timed
注解
Micrometer
是现代Java应用的事实标准指标收集库,与Spring Boot Actuator
深度集成,支持对接 Prometheus、Grafana、Datadog 等监控系统。
添加依赖
xml
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
启用指标端点
yaml
management:
endpoints:
web:
exposure:
include: metrics, prometheus
metrics:
export:
prometheus:
enabled: true
使用 @Timed
注解
java
@Service
public class BusinessService {
@Timed(
value = "business.process.time",
description = "业务处理耗时",
percentiles = {0.5, 0.95, 0.99}
)
public void process() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
访问 /actuator/prometheus
可看到:
ini
# HELP business_process_time_seconds
# TYPE business_process_time_seconds summary
business_process_time_seconds_count{method="process",} 1.0
business_process_time_seconds_sum{method="process",} 0.201
优点
- 标准化指标,支持多维度聚合
- 可视化展示(Grafana)
- 支持报警(Prometheus Alertmanager)
适用场景
- 生产环境性能监控
- 微服务架构下的统一指标体系
5. 使用Java8的Instant
与Duration
Java 8 引入了新的时间 API,更加安全和易用。
java
public void doSomething() {
Instant start = Instant.now();
// 业务逻辑
try {
Thread.sleep(150);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
log.info("耗时:{} ms", duration.toMillis());
}
优点
- 使用现代时间 API,语义清晰
- 线程安全,避免旧 Date 的坑
缺点
- 仍需手动编码
- 性能略低于
nanoTime
适用场景
- 偏好 Java 8+ 新特性的项目
6. 异步方法耗时统计CompletableFuture
对于异步任务,可通过回调机制统计耗时。
java
public CompletableFuture<Void> asyncProcess() {
long start = System.nanoTime();
return CompletableFuture.runAsync(() -> {
// 模拟异步任务
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).whenComplete((result, ex) -> {
long cost = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
log.info("异步任务耗时:{} ms", cost);
});
}
优点
- 适用于非阻塞场景
- 可结合线程池监控
适用场景
- 异步消息处理
- 批量任务调度
7. 使用HandlerInterceptor
统计 Web 请求耗时
在 Web 层通过拦截器统一记录所有 Controller 请求的处理时间。
java
@Component
public class RequestTimeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
request.setAttribute("startTime", System.nanoTime());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
Long start = (Long) request.getAttribute("startTime");
if (start != null) {
long costNanos = System.nanoTime() - start;
long costMillis = TimeUnit.NANOSECONDS.toMillis(costNanos);
String uri = request.getRequestURI();
log.info("HTTP {} {} 耗时: {} ms", request.getMethod(), uri, costMillis);
}
}
}
注册拦截器:
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RequestTimeInterceptor requestTimeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestTimeInterceptor);
}
}
输出:
sql
HTTP GET /api/user/123 耗时: 105 ms
优点
- 全局覆盖所有请求
- 无需修改业务代码
适用场景
- Web 应用整体性能监控
- API 网关层耗时分析
总结
方案 | 侵入性 | 适用场景 | 是否推荐 |
---|---|---|---|
System.currentTimeMillis() |
高 | 临时调试 | ⚠️ 仅调试 |
StopWatch |
中 | 分段计时分析 | ✅ |
AOP + 自定义注解 | 低 | 核心方法监控 | ✅✅✅ 强烈推荐 |
Micrometer @Timed |
低 | 生产监控集成 | ✅✅✅ 生产首选 |
Instant + Duration |
高 | 现代化时间处理 | ✅ |
CompletableFuture 回调 |
中 | 异步任务 | ✅ |
HandlerInterceptor |
低 | Web 请求全局监控 | ✅✅ |
希望这篇文章对你有帮助!如果你有更好的方法,欢迎在评论区分享~ 若有不对的地方也欢迎提出指正。
公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!
📌往期精彩
《工作 5 年没碰过分布式锁,是我太菜还是公司太稳?网友:太真实了!》
《90%的人不知道!Spring官方早已不推荐@Autowired?这3种注入方式你用对了吗?》