严重!Spring AOP Bug导致切面重复执行

开发 前端
在Spring中@Aspect定义的切面最终都会转换为Advisor对象,当代理类在执行时会遍历所有符合添加的Advisor然后从中取出对应的Advice(MethodInterceptor)对象。

环境:Spring6.1.7

1. 问题复现

为了提高代码的复用性和维护性,我们设计了一个通用的抽象的切面父类。这个父类封装了通用的切面逻辑,如日志记录、性能监控、异常处理等。通过继承这个抽象父类,具体的业务切面可以轻松地扩展或定制这些通用功能,而无需从零开始编写切面逻辑。如下示例:

public abstract class CommonAspect {
  @Pointcut("execution(public * com.pack..*.*(..))")
  private void commonPointcut() {}
  @Before("bean(*Service)")
  protected void beforeLog(JoinPoint jp) {
    System.out.println("通用日志记录...") ;
  }
  @Around("@annotation(monitor)")
  protected Object monitorRun(ProceedingJoinPoint pjp, Monitor monitor) throws Throwable {
    Object ret = null ;
    // TODO
    ret = pjp.proceed() ;
    // TODO
    return ret ;
  }
  // other
}

具体切面子类

@Component
@Aspect
public class LogAspect extends CommonAspect {
  @Override
  public void beforeLog(JoinPoint jp) {
    System.out.println("重写日志记录功能") ;
  }
}

当项目运行时出现诡异的问题,既然打印了2次日志信息。

// 测试代码
AnnotationConfigApplicationContext context = ... ;
PersonService ps = context.getBean(PersonService.class) ;
ps.queryById(1L) ;

执行结果

重写日志记录功能
重写日志记录功能
查询Person对象

虽然执行了我们重写的方法,但是日志确输出了2遍。

通过debug分析

容器在启动初始化解析@Aspect切面时,在获取切面类中的所有方法时,会得到两个方法(父类及子类重写的)

图片图片

在这里的getAdvisorMethods方法返回了3个方法,其中2个是父类中的一个是子类重写的方法

图片图片

那么接下来根据这2个方法就会生成对应的Advisor对象。

图片图片

这也就是为什么重复的原因了。Spring并没有判断我当前的这个通知是否是重写父类的方法。

注:在Spring中@Aspect定义的切面最终都会转换为Advisor对象,当代理类在执行时会遍历所有符合添加的Advisor然后从中取出对应的Advice(MethodInterceptor)对象。

既然知道了问题出现的原因,接下来就进行解决该问题。

2. 解决问题

2.1 解决办法1

我们可以在子类重写的方法上再加上通知类型,将切入点设置的不匹配任何方法即可

@Before("execution(public * xxxooo())")
@Override
public void beforeLog(JoinPoint jp) {
  System.out.println("重写日志记录功能") ;
}

在这里重写的方法上,将切入点重新写,该切入点不会匹配任何的方法,这样修改以后再次执行

重写日志记录功能
查询Person对象

正常执行,没有重复输出日志。

2.2 解决办法2

该方法需要将Spring版本升级到6.1.8,在该版本中解决了该问题。来看看在该版本中是如何解决的。

6.1.8版本

核心方法还是在上面debug是看到的getAdvisors方法中

public class ReflectiveAspectJAdvisorFactory {
  public List<Advisor> getAdvisors(...) {
    List<Advisor> advisors = new ArrayList<>();
    for (Method method : getAdvisorMethods(aspectClass)) {
      // 在这里加入了判断(当前方法是否与当前切面中的方法相同)
      // 当遍历到这里的Method是父类中的方法时,这里的getMostSpecificMethod
      // 方法就会判断根据方法的名称再从当前切面类中获取方法(那当然就不同了)
      if (method.equals(ClassUtils.getMostSpecificMethod(method, aspectClass))) {
        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
        if (advisor != null) {
          advisors.add(advisor);
        }
      }
    }
  }
}

图片图片

在6.1.8版本中通过这种方式就排除了父类的方法。

其它版本

图片图片

低于6.1.8版本,都没有相应的判断。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2012-02-10 10:32:33

JavaSpring

2024-04-10 08:59:39

SpringAOP业务

2009-08-24 09:46:40

面向切面编程AOP

2022-02-16 13:46:40

Spring Aop代码注解

2014-03-24 11:07:18

金山毒霸BUG驱动故障

2013-09-17 10:37:03

AOPAOP教程理解AOP

2011-04-26 09:33:04

SpringAOP

2022-04-22 15:03:14

Android漏洞网络攻击

2023-11-07 16:00:25

面向切面编程开发

2024-10-12 14:36:34

2024-05-21 09:55:43

AspectOrientedAOP

2023-09-04 08:36:19

SpringAop日志输出

2020-10-16 09:40:18

顺序Spring AOPHTTP

2009-06-19 13:28:30

Spring AOPSpring 2.0

2022-06-07 07:58:45

SpringSpring AOP

2010-06-11 17:08:05

2021-09-27 10:15:10

故障业务方电脑

2022-02-17 13:39:09

AOP接口方式

2009-05-27 09:32:20

2009-06-22 10:41:34

Spring.AOP
点赞
收藏

51CTO技术栈公众号