AOP 的应用
说明
- SpringBoot 中,org.springframework.boot.autoconfigure.aop.AopAutoConfiguration 默认开启 AOP,故无需显示添加 @EnableAspectJAutoProxy 注解。
- 如需手动关闭,在 application.yaml 中添加 spring.aop.auto=false 即可。
- 通知注解说明:
- @Before 是在所拦截方法执行之前执行一段逻辑。
- @After 是在所拦截方法执行之后执行一段逻辑。
- @Around 是可以同时在所拦截方法的前后执行一段逻辑(写在 point.proceed() 方法前就是之前执行, 写在 point.proceed() 方法后就是之后执行)。
- @AfterReturning finally 块中执行
- @AfterThrowing 捕获到异常会执行
执行顺序:Before, After, AfterReturning, AfterThrowing
- 如果需要在多个通知方法之间传递参数,可以使用 ThreadLocal。详情参考 com.github.mengweijin.quickboot.framework.log.RequestLogAop.java 或者直接使用 @Around 来处理所有的业务逻辑,就不用使用 @Before, @After 等注解了。
使用
- 定义一个类,添加 @Aspect 和 @Component 注解;
- 定义一个无参方法,添加 @Pointcut 注解作为切点,并配置切点表达式(可以配置扫描包、类、注解等)。
- 定义通知方法,根据需要添加通知注解 @Before、@After 等。注解中配置切点方法,并在方法内实现业务逻辑。
Advisor 接口
读过源码的朋友可能会想到使用 org.springframework.aop.Advisor 接口来代替使用 @Aspect 来实现 AOP。
但是从 Advisor 接口中的类注释来看,官方明确说明了这个接口不是给 spring 用户来使用的,所以还是推荐使用注解的方式。
使用示例
java
@Aspect
@Component
public class AopDemo {
@Pointcut("@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController)")
public void logPointCut() {
}
@Before("logPointCut()")
public void before(JoinPoint joinPoint){
// do something
}
/**
* @param joinPoint 切点
* @param object 返回的对象,参数名必须和注解中配置的名称保持一致。
*/
@AfterReturning(pointcut = "logPointCut()", returning = "object")
public void afterReturning(JoinPoint joinPoint, Object object) {
// do something
}
/**
* 拦截异常操作
* @param joinPoint
* @param e
*/
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
// do something
}
}
在 AOP 类中获取方法参数
- 使用 @Before, @After 等注解时,都可以在注解的方法参数中添加 JoinPoint 或 ProceedingJoinPoint(@Around 注解使用) 参数,通过这两个类,都可以获取参数。
- 在添加了 @Aspect 注解的类中的方法中,可以使用如下代码获取参数:
java
MethodInvocation methodInvocation = ExposeInvocationInterceptor.currentInvocation();
Object[] arguments = methodInvocation.getArguments();
JoinPoint 和 ProceedingJoinPoint 中获取参数的原理也都是通过上面的 MethodInvocation 获取的。
在 AOP 类中获取方法上面的注解
需要通过 JoinPoint 来获取方法上面的注解。
java
public static <T extends Annotation> T getMethodAnnotation(JoinPoint joinPoint, Class<T> annotationClass) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
return method == null ? null : method.getAnnotation(annotationClass);
}
你也许会想到使用如下的代码来获取类上面的注解,但这有可能是行不通的。你可能拿不到对应的注解。 因为此时的方法对象并不是原始的方法,而是通过代理生成的代理对象的方法。而代理方法上肯定是没有你添加的注解的。
java
MethodInvocation methodInvocation = ExposeInvocationInterceptor.currentInvocation();
Method method = methodInvocation.getMethod();
Annotation annotation = method.getAnnotation(annotationClass);
于是,你会想到,那能不能直接拿到原始方法,从而获取的原始方法上的注解呢?答案是可以的。
java
MethodInvocation methodInvocation = ExposeInvocationInterceptor.currentInvocation();
Method method = methodInvocation.getMethod();
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
boolean hasAnnotation = AnnotatedElementUtils.hasAnnotation(specificMethod, annotationClass);
从方法和构造函数中获取参数名称
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); String[] parameterNames = discoverer.getParameterNames(method); String[] parameterNames = discoverer.getParameterNames(constructor);