借助AspectJ实现自定义注解打印方法执行时间

270 阅读2分钟

问题场景

工作中常有一些使用面向对象编程方法难以处理的问题,例如打印方法参数、记录方法执行时间、参数校验、事务管理等。这些场景中问题的节点通常分散在各个对象中,被称为横向关注点。处理横向关注点相关问题涉及使用面向切面编程(AOP),在Java生态中,AOP的解决方案有AspectJ、spring-apo等。

本文简要介绍AspectJ为AOP编程引入的几个概念,并通过一个demo展示其具体应用。

关键概念

交接点(Join point)

交接点是定义好的程序执行节点,包括方法和构造器调用、字段访问(get/set)等。

切点(pointcut)

切入点选定一些交接点,并暴露程序在该交接点执行的上下文。

通知(Advice)

通知是可在切点处执行的代码,可以访问到切点暴露的上下文。

切面(Aspect)

切面是一种横切类型,封装了切点、通知和静态横切特征。

Demo:通过自定义注解打印方法执行时间

定义注解

通过如下代码定义打印方法注解:

@Documented  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
public @interface PrintExecutionTime {  
}

其中@Documented表明注解的输出会被类型javadoc之类的工具显示;@Retention指明注解被保留生效的策略,这里为运行时生效;@Target指明注解作用的元素类型,这里为作用于方法。

定义切面类

切面类定义如下,这里使用注解进行切点和通知的声明。

import cn.hutool.core.date.DateTime;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.Around;  
import org.aspectj.lang.annotation.Aspect;  
import org.aspectj.lang.annotation.Pointcut;  
  
import java.util.logging.Logger;  
  
@Aspect  
public class LogExecutionTimeAspect {  
  
private static final Logger logger = Logger.getLogger(LogExecutionTimeAspect.class.getName());  

@Pointcut("@annotation(cc.kakach.annotation.LogExecutionTime) && call (* *(..))")  
public void pointcut() {}  
  
@Around("pointcut()")  
public void logMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {  
    DateTime start = DateTime.now();  
    joinPoint.proceed();  
    var duration = DateTime.now().between(start);  
    logger.info("执行方法{" + joinPoint.getSignature().toString() + "}用时: " + duration.toString());  
}  
  
}

要使切面类生效,还需在pom文件引入aspectj-maven-plugin插件并进行配置:

<plugin>  
<groupId>org.codehaus.mojo</groupId>  
<artifactId>aspectj-maven-plugin</artifactId>  
<version>1.14.0</version>  
<configuration>  
<source>${maven.compiler.source}</source>  
<target>${maven.compiler.target}</target>  
<complianceLevel>${maven.compiler.target}</complianceLevel>  
<encoding>${project.build.sourceEncoding}</encoding>  
</configuration>  
<executions>  
<execution>  
<goals>  
<goal>compile</goal>  
<goal>test-compile</goal>  
</goals>  
</execution>  
</executions>  
</plugin>

使用示例

简单编写测试类对自定义注解及切面进行测试:

public class LogExecutionTimeTest {  
  
  
@LogExecutionTime  
public void doSomething() {  
try {  
Thread.sleep(1000);  
} catch (InterruptedException e) {  
throw new RuntimeException(e);  
}  
}  
  
@Test  
public void testAnnotation() {  
doSomething();  
}  
  
}

测试输出如下:

截图 2023-06-11 17-35-56.png

参考资料

  1. AspectJ官方文档