基于AOP实现记录方法执行时间的小注解
在平时工作中,经常会需要记录一下方法的执行时间,在之前,记录时间的方式会有很多种,比如:
- 第一种:System.currentTimeMillis()
long startTime = System.currentTimeMillis();
Thread.sleep(3000);
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
System.out.println("方法执行时间为:" + executeTime);
控制台输出:
方法执行时间为:3003
- 第二种:cn.hutool.core.date下的DateBetween
Date start = new Date();
Thread.sleep(3000);
Date end = new Date();
DateBetween dateBetween = DateBetween.create(start, end);
long executeTime1 = dateBetween.between(DateUnit.MS);
System.out.println("方法执行时间为:" + executeTime1);
控制台输出:
方法执行时间为:3004
- 第四种:org.springframework.util下的StopWatch
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Thread.sleep(3000);
stopWatch.stop();
System.out.println("方法执行之间为:" + stopWatch.getTotalTimeMillis());
控制台输出:
方法执行时间为:3004
以上三种方式属第一种System.currentTimeMillis最常用,但如果需要记录的方法很多,会在各个方法中出现大量重复的代码,看起来很不优雅。
所以,想着把记录时间的代码都封印在AOP中。废话不多说,先上代码
自定义注解代码:
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RecordMethodInvokeTime {
// 这里需要传入记录时间的单位,可以秒、分、小时来记录时间。
DateUnit value();
}
自定义AOP代码:
@Slf4j
@Aspect
@Component
public class RecordMethodInvokeTimeAspect {
private RecordMethodInvokeTimeAspect() {}
/**
* 可自定义传入在打印日志时的前缀信息。
* @param msg
*/
public static void setPrintMsg(String msg) {
threadLocal.set(msg);
}
private static ThreadLocal threadLocal = new ThreadLocal<>();
/**
* 切点
*/
private static final String POINTCUT = "@annotation(mcu32.czxk.common.util.aop.RecordMethodInvokeTime) && @annotation(recordMethodInvokeTime)";
/**
* 环绕方法,记录方法执行时间。
* @param proceedingJoinPoint
* @param recordMethodInvokeTime
*/
@Around(value = POINTCUT, argNames = "proceedingJoinPoint, recordMethodInvokeTime")
public void recordMethodInvokeTimeAspect(ProceedingJoinPoint proceedingJoinPoint, RecordMethodInvokeTime recordMethodInvokeTime) {
Date startTime = new Date();
String methodName = proceedingJoinPoint.getSignature().getName();
try {
proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
log.error("[{}] invoke failed.", methodName, throwable);
}
Date endTime = new Date();
DateBetween dateBetween = DateBetween.create(startTime, endTime);
// 如果传入了自定义的信息,则会拼接在时间信息的前面
String print = "[{}] method invoke time is [{}], unit [{}]";
if (Objects.nonNull(threadLocal.get())) {
print = StringUtils.join(threadLocal.get(), print);
}
// 打印执行时间
log.info(print, methodName, dateBetween.between(recordMethodInvokeTime.value()), recordMethodInvokeTime.value().name());
}
}
具体使用:
@Autowired
private Test1 test1;
@PostMapping("/testok")
public ApiResult test() {
for (int i = 0; i < 10; i ++) {
final int num = i;
new Thread(() -> {
test1.test(num);
}).start();
}
return ApiResult.ok();
}
@SneakyThrows
@RecordMethodInvokeTime(DateUnit.MS)
public void test(int i) {
RecordMethodInvokeTimeAspect.setPrintMsg("方法" + i + "被执行。");
Thread.sleep(1000);
System.out.println("方法执行了" + i);
}
控制台输出为:
方法执行了3
方法执行了7
方法执行了6
方法执行了0
方法执行了2
方法执行了1
方法执行了8
方法执行了9
方法执行了5
方法执行了4
2020-07-08 16:20:54.183 INFO 21844 --- [ Thread-46] m.c.c.u.a.RecordMethodInvokeTimeAspect : 方法7被执行。[test] method invoke time is [1009], unit [MS]
2020-07-08 16:20:54.183 INFO 21844 --- [ Thread-44] m.c.c.u.a.RecordMethodInvokeTimeAspect : 方法5被执行。[test] method invoke time is [1009], unit [MS]
2020-07-08 16:20:54.184 INFO 21844 --- [ Thread-45] m.c.c.u.a.RecordMethodInvokeTimeAspect : 方法6被执行。[test] method invoke time is [1009], unit [MS]
2020-07-08 16:20:54.184 INFO 21844 --- [ Thread-43] m.c.c.u.a.RecordMethodInvokeTimeAspect : 方法4被执行。[test] method invoke time is [1009], unit [MS]
2020-07-08 16:20:54.183 INFO 21844 --- [ Thread-47] m.c.c.u.a.RecordMethodInvokeTimeAspect : 方法8被执行。[test] method invoke time is [1009], unit [MS]
2020-07-08 16:20:54.184 INFO 21844 --- [ Thread-48] m.c.c.u.a.RecordMethodInvokeTimeAspect : 方法9被执行。[test] method invoke time is [1009], unit [MS]
2020-07-08 16:20:54.184 INFO 21844 --- [ Thread-39] m.c.c.u.a.RecordMethodInvokeTimeAspect : 方法0被执行。[test] method invoke time is [1009], unit [MS]
2020-07-08 16:20:54.184 INFO 21844 --- [ Thread-41] m.c.c.u.a.RecordMethodInvokeTimeAspect : 方法2被执行。[test] method invoke time is [1009], unit [MS]
2020-07-08 16:20:54.183 INFO 21844 --- [ Thread-40] m.c.c.u.a.RecordMethodInvokeTimeAspect : 方法1被执行。[test] method invoke time is [1009], unit [MS]
2020-07-08 16:20:54.184 INFO 21844 --- [ Thread-42] m.c.c.u.a.RecordMethodInvokeTimeAspect : 方法3被执行。[test] method invoke time is [1009], unit [MS]
这样算是在某些业务场景下,避免了大量重复代码的问题。
这里要说一下为什么使用ThreadLocal来设置printMsg。如果不设置printMsg的话,会出现如下情况:
方法执行了0
方法执行了2
方法执行了6
方法执行了3
方法执行了1
方法执行了7
方法执行了9
方法执行了8
方法执行了4
方法执行了5
2020-07-08 16:24:44.420 INFO 21871 --- [ Thread-47] m.c.c.u.a.RecordMethodInvokeTimeAspect : [test] method invoke time is [1011], unit [MS]
2020-07-08 16:24:44.420 INFO 21871 --- [ Thread-46] m.c.c.u.a.RecordMethodInvokeTimeAspect : [test] method invoke time is [1012], unit [MS]
2020-07-08 16:24:44.420 INFO 21871 --- [ Thread-45] m.c.c.u.a.RecordMethodInvokeTimeAspect : [test] method invoke time is [1011], unit [MS]
2020-07-08 16:24:44.420 INFO 21871 --- [ Thread-43] m.c.c.u.a.RecordMethodInvokeTimeAspect : [test] method invoke time is [1012], unit [MS]
2020-07-08 16:24:44.420 INFO 21871 --- [ Thread-42] m.c.c.u.a.RecordMethodInvokeTimeAspect : [test] method invoke time is [1012], unit [MS]
2020-07-08 16:24:44.420 INFO 21871 --- [ Thread-40] m.c.c.u.a.RecordMethodInvokeTimeAspect : [test] method invoke time is [1011], unit [MS]
2020-07-08 16:24:44.420 INFO 21871 --- [ Thread-41] m.c.c.u.a.RecordMethodInvokeTimeAspect : [test] method invoke time is [1011], unit [MS]
2020-07-08 16:24:44.420 INFO 21871 --- [ Thread-39] m.c.c.u.a.RecordMethodInvokeTimeAspect : [test] method invoke time is [1011], unit [MS]
2020-07-08 16:24:44.420 INFO 21871 --- [ Thread-38] m.c.c.u.a.RecordMethodInvokeTimeAspect : [test] method invoke time is [1011], unit [MS]
2020-07-08 16:24:44.420 INFO 21871 --- [ Thread-44] m.c.c.u.a.RecordMethodInvokeTimeAspect : [test] method invoke time is [1011], unit [MS]
请注意看后面打印的日志信息:
[test] method invoke time is [1011], unit [MS]
会发现他们全都长一个样子,在多线程环境下,根本无法区分去此次时间日志是由哪个线程执行的。
所以,如果想在多线程环境下进行使用该注解,最好在方法内部加入
RecordMethodInvokeTimeAspect.setPrintMsg("方法" + i + "被执行。");
以此来声明时间日志的具体信息。
好了,小功能已完成,另外,此aop只适合整体方法的记录时间,如果方法内部需要详细记录某一段代码的执行时间,还需自己单独写。
附带一张LD自拍照,哈哈。