Spring AOP-仅当从Lambda调用joinPoint.proceed时,Pointcut才应用于方法

问题描述

标题不是最好的,但我找不到解决以下问题的好方法

给予

@Aspect
@Component
class MyAspect {
  @Autowired private MyService service;

  @Around("@target(org.springframework.ws.server.endpoint.annotation.Endpoint)")
  public Object aroundEndpoint(ProceedingJoinPoint joinPoint) {
    return service.around(joinPoint::proceed);
  }

  @Around("@target(org.springframework.stereotype.Service)") // And some other expressions to exclude `MyService`
  public Object aroundService(ProceedingJoinPoint joinPoint) throws Throwable {
    // ...
  }
}

@Service
class MyService {
  // My own Callable<T> with Throwable instead of Exception
  public Object around(Callable<?> callable) throws Throwable {
    // Do stuff
    Object returnValue = callable.call();
    // Do stuff
    return returnValue;
  }
}

调用端点方法时,它会被aroundEndpoint拦截。如果我要立即调用joinPoint.proceed(),一切都会按预期进行。但是,如果我将它作为方法引用(或lambda)传递到MyService.around中,然后然后调用它,则它将与我的服务切入点匹配,并且我的围绕服务建议将应用于它

我进行了一些调试,这是我看到的内容:在AspectJExpressionpointcut.matchesthisObjecttargetobject中,在前一种情况下指的是我的端点,而在后一种情况下指的是我的服务。这可能是因为它使用了ExposeInvocationInterceptor.currentInvocation(),并且进行了另一种方法调用

这是一个错误吗?基于代理的方法有一些局限性吗?还是只需要内联MyService.aroundService

解决方法

我重现了您的问题,并且还比较了纯Java + AspectJ中的类似设置(即,没有Spring或Spring AOP,仅使用了切面切入点中使用的两个Spring注释)。在那里不会发生问题。可以肯定,这是Spring AOP特有的。

现在,Spring将AspectJ的切入点匹配与基于代理和委托的自己的AOP框架结合使用。这种情况下的某个地方必须弄乱Spring方面匹配的状态,从而导致您看到的行为。到目前为止,我还没有调试它,但是从现在看来,我建议创建一个问题,并查看维护者对此的看法。

这是我的AspectJ MCVE所提供的证明,这里没有发生问题。顺便说一句,我不得不将包 addLineModified(id) { let index = this.detailsPieces.findIndex((x) => x.id === id); this.detailsPiecesService .getDetailsPieceBylineModified(this.quotation.id,id) .subscribe((element: DetailsPieces) => { this.detailsPieces.splice(index,element); }); } 重命名为aspect,因为在AspectJ中aop是一个保留关键字。但是为了确保它与当前的问题无关,并且也无关,我还在Spring项目中对其进行了重命名。

aspect
package aop.mcve;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {
  public void controllerMethod() {}
}
package aop.mcve;

import org.springframework.stereotype.Service;

@Service
public class MyService {
  public Object delegateTo(MyAspect.Callable<?> callable) throws Throwable {
    return callable.call();
  }

  public void serviceMethod() {}
}
package aop.mcve;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MyAspect {
  private final MyService myService = new MyService();

  @Pointcut("within(aop.mcve..*) && !within(MyAspect) && execution(* *(..))")
  public void inDomain() {}

  @Pointcut("@target(org.springframework.stereotype.Service)")
  public void inService() {}

  @Pointcut("execution(* aop.mcve.MyService.*(..))")
  public void inMyService() {}

  @Pointcut("@target(org.springframework.web.bind.annotation.RestController)")
  public void inController() {}

  @Around("inDomain() && inController()")
  public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("aroundController -> " + joinPoint);
    return myService.delegateTo(joinPoint::proceed);
  }

  @Around("inDomain() && inService() && !inMyService()")
  public Object aroundService(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("aroundService -> " + joinPoint);
    System.out.println("You should never see this message!");
    return joinPoint.proceed();
  }

  public interface Callable<T> {
    T call() throws Throwable;
  }
}

控制台日志:

package aop.mcve;

public class AspectMcveApplication {
  public static void main(String[] args) throws Throwable {
    new MyService().serviceMethod();
    new MyController().controllerMethod();
  }
}

如您所见,通知方法aroundController -> execution(void aop.mcve.MyController.controllerMethod()) 不会像Spring AOP中那样被触发。


更新:我修改了您的MCVE,使其可以在Spring AOP和AspectJ中运行,它在活动时自动检测AspectJ的加载时织入器。我已将此pull request发送给您。