背景
在一些重要接口,可能需要打印出方法的耗时,可能有其他的开源框架的方式来统计,目前的能力也只能想到这种方式了
实现
spring boot 版本 2.4.4
添加依赖
// spring-boot aop
implementation '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)
@Documented
public @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
@Slf4j
public 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
@Slf4j
public 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.659
2022-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.246
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 小时
// 秒测试信息
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
2022-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.392
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 小时
// 分钟信息就不测试了
资料扩展
本文所用到的知识点来自如下文章:
- JAVA8 官网笔记教程 - Instant 类
- Spring 官方文档 - @Aspectj 将参数传给 Advice