背景
在一些重要接口,可能需要打印出方法的耗时,可能有其他的开源框架的方式来统计,目前的能力也只能想到这种方式了
实现
spring boot 版本 2.4.4
添加依赖
// spring-boot aopimplementation 'org.springframework.boot:spring-boot-starter-aop'// hutool 工具使用了里面的一些工具类implementation 'cn.hutool:hutool-all:5.5.4'// 项目使用了 lombok 的 @Slf4j 注解compileOnly 'org.projectlombok:lombok:1.18.18'annotationProcessor 'org.projectlombok:lombok:1.18.18'
编写一个注解,用于标识哪些方法需要打印耗时信息
package cn.mrcode.timewatch;import java.lang.annotation.*;/*** 仅作用在方法上** @author zq*/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface TimeWatch {/*** 该方法名称,业务名称** @return*/String value() default "";}
编写切面
package cn.mrcode.timewatch;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;import java.time.Instant;import java.time.temporal.ChronoUnit;/*** 方法执行时间日志打印*/@Component@Aspect@Slf4jpublic class TimeWatchAspect {// 直接扫描包含 TimeWatch 注解的方法,所以在方法内部就不用再判定注解了// 第一个 @annotation 选择 pointcut, 第二个 @annotation 限定参数类型,表示将目标方法上的注解定义对象注入进来,就不再需要我们自己从 point 上去获取了// 这里也可以直接使用 @annotation(timeWatch) 里面的值则是 方法名称@Around("@annotation(TimeWatch) && @annotation(timeWatch)")public Object handler(ProceedingJoinPoint point, TimeWatch timeWatch) throws Throwable {// 不注入的话,就需要使用这种反射方式自己获取// final TimeWatch timeWatch = ((MethodSignature) point.getSignature()).getMethod().getAnnotation(TimeWatch.class);Instant now = Instant.now();try {Object proceed = point.proceed();return proceed;} finally {Instant end = Instant.now();log.info("[{}] 耗时:{} 毫秒 | {} 秒 | {} 分钟 | {} 小时", timeWatch.value(),now.until(end, ChronoUnit.MILLIS),now.until(end, ChronoUnit.SECONDS),now.until(end, ChronoUnit.MINUTES),now.until(end, ChronoUnit.HOURS));}}}
测试
编写一个 controller 来测试
package cn.mrcode.controller.test;import cn.hutool.core.date.DatePattern;import cn.hutool.core.date.DateUtil;import cn.hutool.core.util.RandomUtil;import cn.mrcode.timewatch.TimeWatch;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Date;import java.util.concurrent.TimeUnit;@RequestMapping("/test")@RestController@Slf4jpublic class TestController {@TimeWatch("timewatch 切面测试")@GetMapping("/timewatch")public Result timewatch() throws InterruptedException {log.info("开始时间:{}", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));long l = System.currentTimeMillis() % 3;// 这里只测试 毫秒、秒、分钟if (l == 0) {int timeout = RandomUtil.randomInt(10, 1000);log.info("休眠 {} 毫秒", timeout);TimeUnit.MILLISECONDS.sleep(timeout);} else if (l == 1) {int timeout = RandomUtil.randomInt(10, 50);log.info("休眠 {} 秒", timeout);TimeUnit.SECONDS.sleep(timeout);} else if (l == 2) {int timeout = RandomUtil.randomInt(1, 3);log.info("休眠 {} 分钟", timeout);TimeUnit.MINUTES.sleep(timeout);}log.info("结束时间:{}", DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));return ResultHelper.ok("ok");}}
访问链接: GET localhost/test/timewatch
由于写的按照当前时间去休眠单位,所以需要多测试几下才能看到 毫秒、秒、分钟的测试信息// 毫秒测试信息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.6592022-08-04 10:33:17.665 INFO 10620 --- [io-11140-exec-1] c.r.d.d.w.c.test.TestController : 休眠 581 毫秒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.2462022-08-04 10:33:18.249 INFO 10620 --- [io-11140-exec-1] c.r.d.d.c.timewatch.TimeWatchAspect : [timewatch 切面测试] 耗时:590 毫秒 | 0 秒 | 0 分钟 | 0 小时// 秒测试信息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.3912022-08-04 10:35:27.391 INFO 10620 --- [io-11140-exec-3] c.r.d.d.w.c.test.TestController : 休眠 35 秒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.3922022-08-04 10:36:02.394 INFO 10620 --- [io-11140-exec-3] c.r.d.d.c.timewatch.TimeWatchAspect : [timewatch 切面测试] 耗时:35003 毫秒 | 35 秒 | 0 分钟 | 0 小时// 分钟信息就不测试了
资料扩展
本文所用到的知识点来自如下文章:
- JAVA8 官网笔记教程 - Instant 类
- Spring 官方文档 - @Aspectj 将参数传给 Advice
