震驚!原來(lái) SpringBoot 里還能這樣統(tǒng)計(jì)耗時(shí),七種姿勢(shì)全解析
在實(shí)際開(kāi)發(fā)中,性能問(wèn)題往往不是“有沒(méi)有”,而是“什么時(shí)候會(huì)爆”。 用戶一句“這個(gè)接口太慢了”,就可能引出一場(chǎng)追蹤性能瓶頸的戰(zhàn)斗。
那我們?cè)撊绾味ㄎ唬?nbsp;關(guān)鍵就在于——準(zhǔn)確統(tǒng)計(jì)方法的執(zhí)行耗時(shí)。
無(wú)論是定位慢 SQL、分析業(yè)務(wù)邏輯,還是優(yōu)化微服務(wù)性能,掌握多種耗時(shí)統(tǒng)計(jì)技巧,都是一名合格后端開(kāi)發(fā)者的必備技能。
今天,就帶大家系統(tǒng)地拆解 Spring Boot 中 7 種方法耗時(shí)統(tǒng)計(jì)姿勢(shì),從最簡(jiǎn)單的手動(dòng)計(jì)時(shí),到高階的 AOP、Micrometer、攔截器全局監(jiān)控,幫你徹底玩轉(zhuǎn)“性能分析”這件事。
一、System.currentTimeMillis() —— 最直接的方式
路徑:/src/main/java/com/icoderoad/demo/service/BasicTimerService.java
這是最原始也是最直觀的方案,用于快速驗(yàn)證某段代碼的執(zhí)行耗時(shí)。
package com.icoderoad.demo.service;
public class BasicTimerService {
public void doSomething() {
long start = System.currentTimeMillis();
// 模擬業(yè)務(wù)邏輯
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
long end = System.currentTimeMillis();
System.out.println("方法執(zhí)行耗時(shí):" + (end - start) + "ms");
}
}優(yōu)點(diǎn)
- 無(wú)需任何依賴(lài)
- 簡(jiǎn)單直接,快速定位問(wèn)題
缺點(diǎn)
- 代碼侵入性強(qiáng)
- 多處統(tǒng)計(jì)時(shí)重復(fù)邏輯多
- 精度依賴(lài)系統(tǒng)時(shí)鐘,可能受 NTP 同步干擾
適用場(chǎng)景
- 本地開(kāi)發(fā)臨時(shí)調(diào)試
- 快速驗(yàn)證小段邏輯
?? 建議:如需更高精度,請(qǐng)使用 System.nanoTime()。
二、StopWatch —— Spring 提供的分段計(jì)時(shí)工具
路徑:/src/main/java/com/icoderoad/demo/util/StopWatchDemo.java
org.springframework.util.StopWatch 是個(gè)被低估的寶藏類(lèi),它可以輕松統(tǒng)計(jì)多個(gè)階段的執(zhí)行時(shí)間,并輸出格式化報(bào)告。
package com.icoderoad.demo.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;
@Slf4j
public class StopWatchDemo {
public void processUserFlow() throws InterruptedException {
StopWatch stopWatch = new StopWatch("用戶處理流程");
stopWatch.start("查詢(xún)用戶");
Thread.sleep(50);
stopWatch.stop();
stopWatch.start("更新緩存");
Thread.sleep(80);
stopWatch.stop();
log.info(stopWatch.prettyPrint());
}
}輸出示例:
StopWatch '用戶處理流程': running time = 130897800 ns
-----------------------------------------
ms % Task name
-----------------------------------------
50.00 38% 查詢(xún)用戶
80.00 62% 更新緩存優(yōu)點(diǎn)
- 支持多任務(wù)分段計(jì)時(shí)
- 輸出清晰、美觀
- 有助于性能對(duì)比分析
適用場(chǎng)景
- 分析復(fù)雜流程中各步驟耗時(shí)占比
三、AOP + 自定義注解 —— 無(wú)侵入耗時(shí)監(jiān)控(推薦)
路徑:
- 注解:
/src/main/java/com/icoderoad/common/annotation/LogCostTime.java - 切面:
/src/main/java/com/icoderoad/common/aspect/CostTimeAspect.java - 示例:
/src/main/java/com/icoderoad/demo/service/UserService.java
通過(guò) AOP(面向切面編程)可優(yōu)雅地實(shí)現(xiàn)無(wú)侵入式耗時(shí)統(tǒng)計(jì)。
第一步:定義注解
package com.icoderoad.common.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogCostTime {
String value() default ""; // 方法描述
long threshold() default 0; // 耗時(shí)閾值(ms)
}第二步:編寫(xiě)切面
package com.icoderoad.common.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.icoderoad.common.annotation.LogCostTime;
import java.util.concurrent.TimeUnit;
@Slf4j
@Aspect
@Component
@Order(1)
public class CostTimeAspect {
@Around("@annotation(logCostTime)")
public Object around(ProceedingJoinPoint pjp, LogCostTime logCostTime) throws Throwable {
String methodName = pjp.getSignature().getName();
long threshold = logCostTime.threshold();
long start = System.nanoTime();
Object result;
try {
result = pjp.proceed();
} finally {
long costMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (threshold > 0 && costMillis > threshold) {
log.warn("? 方法: {} 耗時(shí)超閾值: {} ms (閾值: {} ms)", methodName, costMillis, threshold);
} else {
log.info("方法: {} 耗時(shí): {} ms", methodName, costMillis);
}
}
return result;
}
}第三步:使用注解
package com.icoderoad.demo.service;
import com.icoderoad.common.annotation.LogCostTime;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@LogCostTime(value = "根據(jù)ID查詢(xún)用戶", threshold = 50)
public void getUserById(Long id) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}優(yōu)點(diǎn)
- 幾乎零侵入,只需加注解
- 支持閾值報(bào)警與慢方法監(jiān)控
- 可全局復(fù)用
適用場(chǎng)景
- 核心接口調(diào)用
- 遠(yuǎn)程服務(wù)或數(shù)據(jù)庫(kù)查詢(xún)
四、Micrometer + @Timed —— 生產(chǎn)級(jí)指標(biāo)采集
路徑:/src/main/java/com/icoderoad/demo/monitor/BusinessService.java
Micrometer 是 Spring Boot Actuator 的核心監(jiān)控組件,天然支持 Prometheus/Grafana。
添加依賴(lài)
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>啟用配置
management:
endpoints:
web:
exposure:
include: metrics,prometheus
metrics:
export:
prometheus:
enabled: true使用注解
package com.icoderoad.demo.monitor;
import io.micrometer.core.annotation.Timed;
import org.springframework.stereotype.Service;
@Service
public class BusinessService {
@Timed(value = "business.process.time", description = "業(yè)務(wù)處理耗時(shí)", percentiles = {0.5, 0.95, 0.99})
public void process() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}訪問(wèn) /actuator/prometheus 可見(jiàn)統(tǒng)計(jì)結(jié)果。
優(yōu)點(diǎn)
- 自動(dòng)匯總統(tǒng)計(jì)指標(biāo)
- 可視化展示與報(bào)警
- 適合生產(chǎn)監(jiān)控體系
五、Java 8 Instant + Duration —— 現(xiàn)代時(shí)間 API
路徑:/src/main/java/com/icoderoad/demo/service/DurationService.java
package com.icoderoad.demo.service;
import lombok.extern.slf4j.Slf4j;
import java.time.Duration;
import java.time.Instant;
@Slf4j
public class DurationService {
public void calculate() {
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("耗時(shí):{} ms", duration.toMillis());
}
}優(yōu)點(diǎn)
- 使用現(xiàn)代時(shí)間類(lèi),線程安全
- 可讀性好、語(yǔ)義清晰
六、CompletableFuture 異步任務(wù)耗時(shí)統(tǒng)計(jì)
路徑:/src/main/java/com/icoderoad/demo/async/AsyncCostService.java
package com.icoderoad.demo.async;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
@Slf4j
public class AsyncCostService {
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("異步任務(wù)耗時(shí):{} ms", cost);
});
}
}適用場(chǎng)景
- 批量異步處理
- 消息隊(duì)列或后臺(tái)任務(wù)
七、HandlerInterceptor —— Web 層統(tǒng)一統(tǒng)計(jì)請(qǐng)求耗時(shí)
路徑:
- 攔截器:
/src/main/java/com/icoderoad/web/interceptor/RequestTimeInterceptor.java - 配置類(lèi):
/src/main/java/com/icoderoad/web/config/WebConfig.java
package com.icoderoad.web.interceptor;
import jakarta.servlet.http.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.concurrent.TimeUnit;
@Slf4j
@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 cost = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
log.info("HTTP {} {} 耗時(shí): {} ms", request.getMethod(), request.getRequestURI(), cost);
}
}
}注冊(cè)攔截器:
package com.icoderoad.web.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
import com.icoderoad.web.interceptor.RequestTimeInterceptor;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RequestTimeInterceptor requestTimeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestTimeInterceptor);
}
}優(yōu)點(diǎn)
- 全局生效,覆蓋所有請(qǐng)求
- 無(wú)需改動(dòng)業(yè)務(wù)邏輯
總結(jié)對(duì)比
方案 | 侵入性 | 特點(diǎn) | 推薦度 |
System.currentTimeMillis() | 高 | 簡(jiǎn)單易用 | ?? 調(diào)試用 |
StopWatch | 中 | 分段統(tǒng)計(jì) | ? |
AOP + 注解 | 低 | 自動(dòng)監(jiān)控 | ?? 強(qiáng)烈推薦 |
Micrometer + @Timed | 低 | 生產(chǎn)級(jí)指標(biāo) | ?? 生產(chǎn)首選 |
Instant + Duration | 中 | 語(yǔ)義清晰 | ? |
CompletableFuture | 中 | 異步場(chǎng)景 | ? |
HandlerInterceptor | 低 | Web 全局統(tǒng)計(jì) | ?? 推薦 |
結(jié)語(yǔ)
性能優(yōu)化從來(lái)不是憑感覺(jué),而是以數(shù)據(jù)為依據(jù)的理性決策。 方法耗時(shí)統(tǒng)計(jì),就是找到問(wèn)題根源的第一步。
當(dāng)你掌握了上面這 7 種方式—— 無(wú)論是本地調(diào)試、生產(chǎn)監(jiān)控、異步任務(wù)還是全局請(qǐng)求追蹤,都能應(yīng)對(duì)自如。





























