SpringBoot 中的 7 种耗时统计方式,你用过几种?

前言

在日常开发中,经常会遇到一些性能问题。

比如用户反馈:"这个页面加载好慢啊!" 这个时候,你该怎么办?

首先就得找出到底是哪个方法、哪段代码执行时间过长。

只有找到了瓶颈,才能对症下药进行优化。所以说,方法耗时统计是性能优化中非常重要的一环。

接下来,我就给大家介绍七种实用的实现方式,从简单到复杂,总有一种适合你!


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的InstantDuration

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种注入方式你用对了吗?》

《终于找到 Axios 最优雅的封装方式了,再也不用写重复代码了》

《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》

相关推荐
yeyong2 小时前
如何找到一个陌生服务器上的grafana-server是谁启动的
后端
小蒜学长2 小时前
springboot宠物领养救助平台的开发与设计(代码+数据库+LW)
java·数据库·spring boot·后端·宠物
武子康2 小时前
大数据-106 Spark Graph X案例:1图计算、2连通图算法、3寻找相同用户 高效分区、负载均衡与迭代优化
大数据·后端·spark
小羊在睡觉2 小时前
Go语言爬虫:爬虫入门
数据库·后端·爬虫·golang·go
fendouweiqian3 小时前
pom.xml 不在根目录,idea无法识别项目处理方案
xml·java·intellij-idea
JAVA学习通3 小时前
微服务项目->在线oj系统(Java-Spring)----7.0
java·spring·微服务
poemyang3 小时前
从MESA模型到锁升级:synchronized性能逆袭的底层逻辑
java·并发编程·java并发编程
用户0332126663673 小时前
Java 给 PDF 设置背景色和背景图片
java