背景

在一些重要接口,可能需要打印出方法的耗时,可能有其他的开源框架的方式来统计,目前的能力也只能想到这种方式了

实现

spring boot 版本 2.4.4

添加依赖

  1. // spring-boot aop
  2. implementation 'org.springframework.boot:spring-boot-starter-aop'
  3. // hutool 工具使用了里面的一些工具类
  4. implementation 'cn.hutool:hutool-all:5.5.4'
  5. // 项目使用了 lombok 的 @Slf4j 注解
  6. compileOnly 'org.projectlombok:lombok:1.18.18'
  7. annotationProcessor 'org.projectlombok:lombok:1.18.18'

编写一个注解,用于标识哪些方法需要打印耗时信息

  1. package cn.mrcode.timewatch;
  2. import java.lang.annotation.*;
  3. /**
  4. * 仅作用在方法上
  5. *
  6. * @author zq
  7. */
  8. @Target(ElementType.METHOD)
  9. @Retention(RetentionPolicy.RUNTIME)
  10. @Documented
  11. public @interface TimeWatch {
  12. /**
  13. * 该方法名称,业务名称
  14. *
  15. * @return
  16. */
  17. String value() default "";
  18. }

编写切面

  1. package cn.mrcode.timewatch;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.aspectj.lang.ProceedingJoinPoint;
  4. import org.aspectj.lang.annotation.Around;
  5. import org.aspectj.lang.annotation.Aspect;
  6. import org.springframework.stereotype.Component;
  7. import java.time.Instant;
  8. import java.time.temporal.ChronoUnit;
  9. /**
  10. * 方法执行时间日志打印
  11. */
  12. @Component
  13. @Aspect
  14. @Slf4j
  15. public class TimeWatchAspect {
  16. // 直接扫描包含 TimeWatch 注解的方法,所以在方法内部就不用再判定注解了
  17. // 第一个 @annotation 选择 pointcut, 第二个 @annotation 限定参数类型,表示将目标方法上的注解定义对象注入进来,就不再需要我们自己从 point 上去获取了
  18. // 这里也可以直接使用 @annotation(timeWatch) 里面的值则是 方法名称
  19. @Around("@annotation(TimeWatch) && @annotation(timeWatch)")
  20. public Object handler(ProceedingJoinPoint point, TimeWatch timeWatch) throws Throwable {
  21. // 不注入的话,就需要使用这种反射方式自己获取
  22. // final TimeWatch timeWatch = ((MethodSignature) point.getSignature()).getMethod().getAnnotation(TimeWatch.class);
  23. Instant now = Instant.now();
  24. try {
  25. Object proceed = point.proceed();
  26. return proceed;
  27. } finally {
  28. Instant end = Instant.now();
  29. log.info("[{}] 耗时:{} 毫秒 | {} 秒 | {} 分钟 | {} 小时", timeWatch.value(),
  30. now.until(end, ChronoUnit.MILLIS),
  31. now.until(end, ChronoUnit.SECONDS),
  32. now.until(end, ChronoUnit.MINUTES),
  33. now.until(end, ChronoUnit.HOURS)
  34. );
  35. }
  36. }
  37. }

测试

编写一个 controller 来测试

  1. package cn.mrcode.controller.test;
  2. import cn.hutool.core.date.DatePattern;
  3. import cn.hutool.core.date.DateUtil;
  4. import cn.hutool.core.util.RandomUtil;
  5. import cn.mrcode.timewatch.TimeWatch;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.web.bind.annotation.GetMapping;
  9. import org.springframework.web.bind.annotation.RequestMapping;
  10. import org.springframework.web.bind.annotation.RestController;
  11. import java.util.Date;
  12. import java.util.concurrent.TimeUnit;
  13. @RequestMapping("/test")
  14. @RestController
  15. @Slf4j
  16. public class TestController {
  17. @TimeWatch("timewatch 切面测试")
  18. @GetMapping("/timewatch")
  19. public Result timewatch() throws InterruptedException {
  20. log.info("开始时间:{}", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
  21. long l = System.currentTimeMillis() % 3;
  22. // 这里只测试 毫秒、秒、分钟
  23. if (l == 0) {
  24. int timeout = RandomUtil.randomInt(10, 1000);
  25. log.info("休眠 {} 毫秒", timeout);
  26. TimeUnit.MILLISECONDS.sleep(timeout);
  27. } else if (l == 1) {
  28. int timeout = RandomUtil.randomInt(10, 50);
  29. log.info("休眠 {} 秒", timeout);
  30. TimeUnit.SECONDS.sleep(timeout);
  31. } else if (l == 2) {
  32. int timeout = RandomUtil.randomInt(1, 3);
  33. log.info("休眠 {} 分钟", timeout);
  34. TimeUnit.MINUTES.sleep(timeout);
  35. }
  36. log.info("结束时间:{}", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
  37. return ResultHelper.ok("ok");
  38. }
  39. }

访问链接: GET localhost/test/timewatch

  1. 由于写的按照当前时间去休眠单位,所以需要多测试几下才能看到 毫秒、秒、分钟的测试信息
  2. // 毫秒测试信息
  3. 2022-08-04 10:33:17.664 INFO 10620 --- [io-11140-exec-1] c.r.d.d.w.c.test.TestController : 开始时间:2022-08-04 10:33:17.659
  4. 2022-08-04 10:33:17.665 INFO 10620 --- [io-11140-exec-1] c.r.d.d.w.c.test.TestController : 休眠 581 毫秒
  5. 2022-08-04 10:33:18.246 INFO 10620 --- [io-11140-exec-1] c.r.d.d.w.c.test.TestController : 结束时间:2022-08-04 10:33:18.246
  6. 2022-08-04 10:33:18.249 INFO 10620 --- [io-11140-exec-1] c.r.d.d.c.timewatch.TimeWatchAspect : [timewatch 切面测试] 耗时:590 毫秒 | 0 | 0 分钟 | 0 小时
  7. // 秒测试信息
  8. 2022-08-04 10:35:27.391 INFO 10620 --- [io-11140-exec-3] c.r.d.d.w.c.test.TestController : 开始时间:2022-08-04 10:35:27.391
  9. 2022-08-04 10:35:27.391 INFO 10620 --- [io-11140-exec-3] c.r.d.d.w.c.test.TestController : 休眠 35
  10. 2022-08-04 10:36:02.392 INFO 10620 --- [io-11140-exec-3] c.r.d.d.w.c.test.TestController : 结束时间:2022-08-04 10:36:02.392
  11. 2022-08-04 10:36:02.394 INFO 10620 --- [io-11140-exec-3] c.r.d.d.c.timewatch.TimeWatchAspect : [timewatch 切面测试] 耗时:35003 毫秒 | 35 | 0 分钟 | 0 小时
  12. // 分钟信息就不测试了

资料扩展

本文所用到的知识点来自如下文章: