问题场景
工作中常有一些使用面向对象编程方法难以处理的问题,例如打印方法参数、记录方法执行时间、参数校验、事务管理等。这些场景中问题的节点通常分散在各个对象中,被称为横向关注点。处理横向关注点相关问题涉及使用面向切面编程(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();
}
}
测试输出如下: