基于AOP实现记录方法执行时间的小注解

基于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自拍照,哈哈。


微信头像.jpg

你可能感兴趣的:(基于AOP实现记录方法执行时间的小注解)