摘要:@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface LogCostTime { String value default ""; long thresh
在日常开发中,经常会遇到一些性能问题。
比如用户反馈:“这个页面加载好慢啊!” 这个时候,你该怎么办?
首先就得找出到底是哪个方法、哪段代码执行时间过长。
只有找到了瓶颈,才能对症下药进行优化。所以说,方法耗时统计是性能优化中非常重要的一环。
接下来,我就给大家介绍七种实用的实现方式,从简单到复杂,总有一种适合你!
这是最原始但最直接的方式,适用于快速验证某段 #技术分享代码的执行时间。
public void doSomething { long start = System.currentTimeMillis;try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread.interrupt; }long end = System.currentTimeMillis; System.out.println("方法执行耗时:" +}本地开发调试快速验证某段逻辑耗时⚠️ 注意:该方法基于系统时间,不适用于高精度计时。推荐使用 System.nanoTime 替代(见后文补充)。
Spring 提供了 org.springframework.util.StopWatch 类,支持分段计时和格式化输出,适合需要统计多个子任务耗时的场景。
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); }输出示例:
StopWatch '用户处理流程': running time = 130897800 ns 50.00 38% 查询用户 80.00 62% 更新缓存通过面向切面编程(AOP),可以实现对指定方法的无侵入式耗时监控。
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface LogCostTime { String value default ""; long threshold default 0;}@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 。
第三步:使用注解@Servicepublic 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); } }输出:
WARN ... 方法: UserService.getUserById(根据ID查询用户) 耗时超阈值: 102 ms (阈值: 50 ms)优点核心服务方法远程调用(RPC/HTTP)数据库查询复杂计算逻辑Micrometer 是现代 Java 应用的事实标准指标收集库,与 Spring Boot Actuator 深度集成,支持对接 Prometheus、Grafana、Datadog 等监控系统。
添加依赖io.micrometermicrometer-coreorg.springframework.bootspring-boot-starter-actuator启用指标端点management: endpoints: web: exposure: include: metrics, prometheus metrics: export: prometheus: enabled: true@Servicepublic 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 可看到:
business_process_time_seconds_count{method="process",} 1.0business_process_time_seconds_sum{method="process",} 0.201优点适用场景Java 8 引入了新的时间 API,更加安全和易用。
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); }优点缺点仍需手动编码性能略低于 nanoTime适用场景对于异步任务,可通过回调机制统计耗时。
public CompletableFuture 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); }); }优点在 Web 层通过拦截器统一记录所有 Controller 请求的处理时间。
@Componentpublic 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); } } }注册拦截器:
@Configurationpublic class WebConfig implements WebMvcConfigurer { @Autowired private RequestTimeInterceptor requestTimeInterceptor;@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(requestTimeInterceptor); } }| 方案 | 侵入性 | 适用场景 | 是否推荐 | | ---
| System.currentTimeMillis | 高 | 临时调试 | ⚠️ 仅调试 | | StopWatch | 中 | 分段计时分析 | ✅ | | AOP +
| Micrometer @Timed | 低 | 生产监控集成 | ✅✅✅ 生产首选 | | Instant +
| CompletableFuture 回调 | 中 | 异步任务 | ✅ | | HandlerInterceptor | 低 | Web 请求全局监控 | ✅✅ |
希望这篇文章对你有帮助!如果你有更好的方法,欢迎在评论区分享~ 若有不对的地方也欢迎提出指正。
《工作 5 年没碰过分布式锁,是我太菜还是公司太稳?网友:太真实了!》
《90%的人不知道!Spring 官方早已不推荐@Autowired?这3种注入方式你用对了吗?》
《终于找到 Axios 最优雅的封装方式了,再也不用写重复代码了》
《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》
来源:墨码行者