AOP 是面向切面编程,Spring AOP 是通过 JDK 动态代理和 CGLIB 来实现 AOP 的,如果目标类实现了接口就用 JDK 动态代理,如果没有实现接口就会用 CGLIB 来生成代理类。Spring AOP 中有一些概念需要先知道:
- 切面(Aspect):在 Spring 表现为一个 bean,用来集中定义切入点
- 连接点(Join Point):Spring AOP 只支持方法级别的连接点,方法连接点就是你要进行切入或者增强的方法
- 切入点(Pointcut):在 Spring 中表现为方法上的注解,配合切入点表达式来定义要切入或增强的方法
- 通知:在 Spring 中表现为一个方法,定义在切面 bean 中
要使用 Spring AOP,除了其他的 Spring 依赖外,你需要引入如下依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.10</version>
</dependency>
使用 Spring AOP 分为如下几步:
- 开启 AspectJ 注解支持
在 @Configuration bean 上标注 @EnableAspectJAutoProxy,它会往 Spring IoC 中注册一个 BeanPostProcessor 的实现类 AnnotationAwareAspectJAutoProxyCreator,Spring 非常多的功能都是通过 BeanPostProcessor 实现类来完成的,它是 Spring 提供的扩展点之一。
@Configuration
@EnableAspectJAutoProxy
public class MyConfig{
}
- 定义切面
通过 @Aspect 注解来定义切面,通知要将 bean 交给 Spring IoC 容器管理。
@Component
@Aspect
public class MyAspect{
}
- 定义切入点和通知
定义好切面之后,在切面里面定义切入点和通知。
// 切面
@Aspect
@Component
public class MyAspectJ {
// 可复用的切入点
@Pointcut("execution(public String org.config.AspectJTest.doSomething(..))")
private void recordLog() {}
// 前置通知
@Before("recordLog()")
public void before(){
System.out.println("这是一个前置通知");
}
// 后置通知
@AfterReturning(pointcut = "recordLog()", returning = "input")
public void afterReturning(Object input){
System.out.println("这是一个后置通知,连接点方法的返回值是:" + input);
}
// 异常通知
@AfterThrowing(pointcut = "recordLog()", throwing = "ex")
public void afterThrowing(Exception ex){
System.out.println("这是一个异常通知,连接点方法抛出的异常是:" + ex.getMessage());
}
// 最终通知
@After("recordLog()")
public void after(){
System.out.println("这是一个最终通知");
}
// 环绕通知
@Around("recordLog()")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("这是一个环绕通知,执行方法前业务...");
Object proceed = null;
try {
proceed = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("这是一个环绕通知,执行方法后业务...");
return proceed;
}
}
这里使用 execution 类型的切入点表达式,Spring AOP 还支持很多种类型的切入点表达式,但是 execution 类型是最常用的,@Pointcut 定义了一个切入点,里面是一个切入点表达式,它指定了对哪些类的哪些方法进行增强。
Spring AOP 提供了 5 种类型的通知:
- 前置通知,在目标方法执行执行
- 后置通知:在目标方法执行结束后执行,如果目标方法异常结束则不会执行,它可以获取到目标方法的返回值
- 异常通知:在目标方法异常退出后执行,它可以获取到目标方法抛出的异常
- 最终通知:在目标方法执行结束后执行,它和后置通知的区别是最终通知即使是目标方法异常退出也会执行
- 环绕通知:环绕通知是最通用的一种通知,它的返回值必须是 Object,方法的第一个参数必须是 ProceedingJoinPoint,你通过调用 ProceedingJoinPoint.processd() 来执行目标方法
- 定义连接点
连接点就是你要增强的类。
@Component
public class AspectJTest implements MyComponent {
public String doSomething(String input){
System.out.println("bussiness method...");
return "output";
}
}
- 运行测试
public static void main(String[] args) {
System.out.println("Hello World!");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("org.config");
context.refresh();
AspectJTest aspectJTest = context.getBean(AspectJTest.class);
String output = aspectJTest.doSomething("input");
System.out.println(output);
}
你会看到如下效果:
这是一个环绕通知,执行方法前业务...
这是一个前置通知
bussiness method...
这是一个后置通知,连接点方法的返回值是:output
这是一个最终通知
这是一个环绕通知,执行方法后业务...
output