Spring AOP在单一连接点(JoinPoint)上使用多个带绑定的Around切面方法(advice)时报错

新手上路,请多包涵

在一个方法上使用2个注解,并用两个不同的切面分别去处理这两个注解中的值,连接点方法如下所示:

@CacheFetch(cacheName = CacheManager.CACHE_DATASOURCE_INFO)
@TenantAware(method = OperationMethod.OPERATION, operation = OperationType.GET)
public DataSourceInfo fetchDataSource(String sourceId) {...}

切面方法1:

@Around("within(com.xx.yy.zz..*) && @annotation(fetch)")
public Object fetchFromCache(ProceedingJoinPoint pjp, CacheFetch fetch) throws Throwable {...}

切面方法2:

@Around("isXXX() && @annotation(tenantAware)")
public Object handleTenantAware(ProceedingJoinPoint pjp, TenantAware tenantAware) throws Throwable {...}

两个切面方法写在了2个不同的Aspect类中,并实现了Ordered接口。当执行到切入点方法时,报出以下错误:

java.lang.IllegalStateException: Required to bind 2 arguments, but only bound 1 (JoinPointMatch was NOT bound in invocation)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.argBinding(AbstractAspectJAdvice.java:591)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:616)
    at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:671)

如果移除其中掉一个方法的注解,则另一个切面方法可以正常执行。网上搜索内容很少,相关的只有很老的Spring版本中有人问到。

使用的Spring版本为4.1.6,尝试升级到4.1.9 或4.3.20问题仍然存在。

按我的理解这样写应该没有什么问题,但不知为何报错还没人提出,不确定是bug还是用法问题。希望有人能帮忙,多谢!

阅读 11.9k
3 个回答

如果是实现了Ordered接口,它默认值如下。

public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE + 1;
}

如果是两个Ordered的话,拦截器估计不能判断谁优先, @Order 时可指定一个int型的value属性,该属性值越小,则优先级越高。
例如:

@Aspect
@Order(1)
public class AspectFirst {
}

@Aspect
@Order(2)
public class AspectSecond {
}

你试试看

新手上路,请多包涵

你的问题在于利用了注解的形式@annotation(tenantAware)")去获取参数,在只切一次的情况下没有问题,但是切面嵌套就不行了,你可以利用传统的方式去获取这个tenantAware就行了。

新手上路,请多包涵

挖,跟随源码很容易可以发现,在两个指定了不同@Order后,代理的顺序被明显的区分出来。而代理后的方法没有注解,导致第二个切面参数个数不匹配。

  • 两个改进方案:(前提是一定需要指定切面顺序,且想使用注解切面)

    1. 切点的匹配方式不使用@annotation,使用其他方式匹配到切点,使用反射获取注解内容;
    2. 在一个方法上只使用一个切面注解(这些注解最好能单独维护一个类,用于切面排序)
@Component
public class ReportCron implements ReportCronService {
    // 注入『自己』,方便引用自己的方法时也进入切面
    // 注入自己时,不能直接注入自己,可以注入接口形式
    @Autowired
    ReportCronService service;
    @MyAnnotation_1()
    public void xxx() {
        service.yyy();
    }
    @MyAnnotation_2()
    @Override
    public void yyy() {
        // TODO 业务逻辑
    }  
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题