Spring AOP:如何排除由于其他人执行而导致的不必要的@Pointcut(@Around advice)执行@Pointcuts/advices

Spring AOP: How exclude an unnecessary @Pointcut (@Around advice) execution due the execution from others @Pointcuts/advices

我正在与:

我有以下正常流程:

关于 AOP 我有以下一对:

场景如下:

@Serviceclass有一些方法如:deletesaveupdatefindOneById。它们在同一个 class.

中一起 声明

对于 deleteupdateAOP 等方法,我使用 @Before@Around 建议来调用 findOneById 方法。

原因是没有意义 执行 deleteupdate 方法(考虑 Rest 场景)如果实体不存在。因此,通过 advice 必须抛出异常,假设异常 A,它必须在 @ControllerAdvice

中处理

实际上应用了与 save 方法相同的方法。因此,在 execute save 方法 other @Before or @Around advice 调用 findOneById 方法。如果实体已经存在,则必须抛出异常,假设异常 B,它必须在 @ControllerAdvice

中处理

观察我有 3 个 points/3advices 使用 findOneById 方法。它检查是否存在实体。

例如:

    @Pointcut(value=
    "execution(* mypackage.PersonaServiceImpl.saveOne(otherpackage.Persona)) 
    && args(persona)")
    public void saveOnePointcut(Persona persona){}

    @Pointcut(value=
    "execution(*  
    mypackage.PersonaServiceImpl.updateOne(otherpackage.Persona)) 
    && args(persona)")
    public void updateOnePointcut(Persona persona){}

    @Pointcut(value="execution(*  
    mypackage.PersonaServiceImpl.deleteOne(String)) && args(id)")
    public void deleteOnePointcut(String id){}

同样:这 3 个建议使用或执行 findOneById 方法。

问题是当我添加一个新的切入点时,例如:

@Pointcut(value="execution(*    
mypackage.PersonaServiceImpl.findOneById(String)) 
&& args(id)")
public void findOneByIdPointcut(String id){}

我创建了这个切入点来检查一个实体是否已经存在,如果它不存在它必须抛出一个 C 类型的异常(它用于 classic 404)。

似乎多余 执行 findOneById 方法通过 @Before@AroundfindOneById 方法本身的建议。但是我需要这个来达到 loggingaudit 的目的,并创建 C 类型的异常。它必须由一些 @ControllerAdvice

处理

问题 是在执行 delete/update/save 方法的其他建议时(记住他们调用并执行 findOneById 方法)我的 findOneByIdPointcut 被执行 不必要地

我需要更改切入点声明以指示如下内容:

@Pointcut(Alpha)
public void findOneByIdPointcut(String id){}

其中 Alpha 是:

@ServicefindOneById方法执行before/around建议,但是从不 如果它的调用是根据
的其他建议完成的 PersonaServiceAspect class.

我尝试了很多 !execution!within 组合的方法,但没有结果。

Even 当我只创建 one 切入点时 拦截 所有 @Service 的方法及其各自独特的 @Around 建议,并通过 ProceedingJoinPoint proceedingJoinPoint 参数我能够检查调用了哪个方法,然后执行相应的控制。但这种行为再次发生。

意思是,通过以下:

@Around("PersonaServicePointcut.anyMethodPointcut()")
    public Object aroundAdviceAnyMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{

其中 anyMethodPointcutexecution(* mypackage.PersonaServiceImpl.*(..))

是否可以实现这种方法?怎么样?

谢谢。

您可以使用

在切入点表达式的控制流中排除切入点
!cflow(<pointcut>)

在您的示例中,您想排除 findOneById 的那些执行,其中执行在您自己的建议的控制流中。如果你的建议应用于切入点表达式

@Pointcut("saveOnePointcut() || updateOnePointcut() || deleteOnePointcut()")
public void combinedPointcut() {}

你可以排除它:

!cflow(combinedPointcut())

或者简单地从所有通知执行的控制流中排除所有控制流,您可以使用:

!cflow(adviceexecution())

基于组合的切入点表达式,您的周围查找建议如下所示:

@Around("findOneByIdPointcut() && !cflow(combinedPointcut())")
public void aroundFind() {
    ....
}

请注意,您不能在示例中组合切入点表达式,因为它们绑定切入点参数。您需要删除 && args(...) 部分并将它们直接移动到通知切入点表达式。您不能将 args(paramName) 等绑定切入点表达式与 ||(或)运算符结合使用,因此您需要为这些情况创建单独的建议。如果愿意,您仍然可以将这些建议中的大部分工作委托给一个方法。请参阅此类委托的示例

如果我理解正确,你希望 findOneByIdPointcut 不会在你的建议中调用,但应该在所有其他情况下触发。

一种方法是将 callwithin 切入点结合使用。 call 会将连接点向上移动到调用方方法,within 将从目标连接点排除通知。

因此您的方面代码可能如下所示:

public class ControllerAspect {

    ...

    @Pointcut(value = "execution(* PersonaServiceImpl.deleteOne(String)) && args(id)")
    public void deleteOnePointcut(String id)
    {
    }

    @Pointcut(value = "call(* PersonaServiceImpl.findOneById(String)) && args(id)")
    public void findOneByIdPointcut(String id)
    {
    }

    @Before(value = "findOneByIdPointcut(id) && !within(ControllerAspect)")
    public void beforeFindOneByIdAdvice(String id)
    {
       //do some logic here
    }


    @Before(value = "deleteOnePointcut(id)")
    public void beforeDeleteOneAdvice(String id)
    {
        Persona persona = personaService.findOneById(id);
        //check that persona is not null
    }
}

另一种方法是使用 cflow 切入点,如@Nándor Előd Fekete 回答中所述,但您应该注意,在这种情况下,堆栈中建议执行下方的所有 beforeFindOneByIdAdvice 连接点都将被排除也。所以这可能会给你带来一些意想不到的结果。

您可以在“Aspect oriented programming - what is 'cflow'?”答案中查看 cflow 的工作原理。