AOP的各种通知
在使用Spring的AOP特性中,Spring为我们提供了多种通知注解,方便我们灵活地用各种方式做切入
首先我们定义一个用于简单除法运算的主业务类和主业务方法,来方便我们演示各种通知方法的切入效果
public class MyCalculator {
public int calculate(int a, int b) {
int result = a / b;
System.out.println("切入点方式执行,结果为" + result);
return result;
}
}
前置通知
前置通知的作用是将通知方法的逻辑切入到切入点方法之前,使用的时候将@Before
注解添加到通知方法上,并在注解的属性中指定切入点方法即可
@Aspect
public class LogAspects {
@Before("execution(public int com.blackball.component.MyCalculator.calculate(int,int))")
public void beforeLog() {
System.out.println("执行前置通知");
}
}
执行结果如下
后置通知
后置通知的作用是将通知方法的逻辑切入到切入点方法之后,使用@After
注解,使用的方法和前置通知一样
@Aspect
public class LogAspects {
@After("execution(public int com.blackball.component.MyCalculator.calculate(int,int))")
public void afterLog() {
System.out.println("执行后置通知");
}
}
执行结果如下
返回通知
返回通知的作用是得到方法的返回值,然后在方法结束后执行通知方法,使用@AfterReturning
注解,在通知方法的参数列表添加一个参数作为通知获取的返回值,并在注解的returning属性指定这个参数
@Aspect
public class LogAspects {
@AfterReturning(pointcut = "execution(public int com.blackball.component.MyCalculator.calculate(int,int))", returning = "result")
public void returnLog(int result) {
System.out.println("执行返回通知,返回结果为" + result);
}
}
执行结果如下
异常通知
异常通知的作用是在切入点方法出现异常时,获取该异常,然后执行通知方法进行处理,使用@AfterThrowing
注解,使用方法和返回通知相似,在通知方法的参数列表添加一个异常作为通知获取的异常,并在注解的throwing属性指定这个异常
@Aspect
public class LogAspects {
@AfterThrowing(pointcut = "execution(public int com.blackball.component.MyCalculator.calculate(int,int))", throwing = "exception")
public void exceptionLog(Exception exception) {
System.out.println("执行异常通知,异常为" + exception);
}
}
我们在测试代码中调用主业务方法,并且制造一个除0异常
@SpringBootTest
class DemoApplicationTests {
@Autowired
MyCalculator calculator;
@Test
void contextLoads() {
calculator.calculate(10, 0);
}
}
执行结果如下
环绕通知
环绕通知的作用是在通知内部,手动地调用切入点方法,自定义切入点方法执行的时机,可以在切入点方法执行的前后都添加增强逻辑,使用@Around
,在通知方法的参数列表添加一个 ProceedingJoinPoint
对象参数作为切入点方法对象,在通知方法中,调用ProceedingJoinPoint
对象的proceed
方法,执行切入点方法,注意环绕通知的返回类型需要与切入点方法匹配
@Aspect
public class LogAspects {
@Around("execution(public int com.blackball.component.MyCalculator.calculate(int,int))")
public int aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("执行环绕通知之前");
int result = (int) joinPoint.proceed();
System.out.println("执行环绕通知之后,结果为" + result);
return result;
}
}
执行结果如下
获取切入点方法的信息
有时候我们需要在通知方法中获取切入点方法的信息进行处理,可以使用JoinPoint
接口,在通知方法的参数类别中添加一个JoinPoint
对象,使用JoinPoint
对象可以获取切入点方法的信息
@Aspect
public class LogAspects {
@Before("execution(public int com.blackball.component.MyCalculator.calculate(int,int))")
public void beforeLog(JoinPoint joinPoint) {
System.out.println(Arrays.toString(joinPoint.getArgs()));
System.out.println(joinPoint.getSignature());
}
}
执行结果如下
需要注意的是,当通知方法的参数列表有多个参数时,如使用返回通知或异常通知时,JoinPoint
对象参数需要在参数列表的第一个位置才能生效
通知的执行顺序
现在我们将以上的通知方法都添加到切面类中一起生效,并测试他们的执行顺序
@Aspect
public class LogAspects {
@Pointcut("execution(public int com.blackball.component.MyCalculator.calculate(int,int))")
public void pointCut() {
}
@Before("pointCut()")
public void beforeLog() {
System.out.println("执行前置通知");
}
@After("pointCut()")
public void afterLog() {
System.out.println("执行后置通知");
}
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void returnLog(int result) {
System.out.println("执行返回通知,返回结果为" + result);
}
@AfterThrowing(pointcut = "pointCut()", throwing = "exception")
public void exceptionLog(Exception exception) {
System.out.println("执行异常通知,异常为" + exception);
}
@Around("pointCut()")
public int aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("执行环绕通知之前");
int result = (int) joinPoint.proceed();
System.out.println("执行环绕通知之后");
return result;
}
}
正常情况
正常情况下的执行结果如下
可以看到正常情况下通知的执行顺序是:
- 环绕通知(前)
- 前置通知
- 切入点方法
- 返回通知
- 后置通知
- 环绕通知(后)
异常情况
异常情况下的执行结果如下
可以看到异常情况下通知的执行顺序是:
- 环绕通知(前)
- 前置通知
- 切入点方法(发生异常前部分)
- 异常通知
- 后置通知
注意以上执行顺序的测试结果基于Spring 5,Spring 4的测试结果会有所不同