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



我正在使用:

  • Spring Framework 4.3.3
  • AspectJ 1.8.9

我有以下正常过程:

  • @Controller->@Service->@Repository

我有以下关于AOP的配对:

  • PersonaServicePointcut
    • PersonaServiceAspect

场景如下:

@Service类有一些方法,如:deletesaveupdatefindOneById。它们在同一类中一起声明为

对于诸如deleteupdateAOP之类的方法,我使用@Before@Around建议来调用findOneById方法。

原因是如果实体不存在,执行deleteupdate方法(考虑Rest场景(是没有意义的。因此,通过该advice,必须抛出异常,比如exceptionA,它必须在@ControllerAdvice 中处理

实际上,save方法也采用了相同的方法。因此,在执行之前,执行save方法其他@Before@Around建议,再次调用findOneById方法。如果实体已经存在,则必须抛出异常,比如exceptionB,它必须在@ControllerAdvice 中处理

注意,我有3点/3使用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的异常(对于经典404(。

似乎是多余的通过针对findOneById方法本身的@Before@Around建议执行findOneById方法。但我需要它来实现loggingaudit的目的,并创建C类型的异常。它必须由一些@ControllerAdvice 处理

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

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

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

其中Alpha为:

@ServicefindOneById方法执行before/around建议,如果其调用是从
的其他建议中完成的,则从不执行
CCD_ 46类。

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

即使当我只创建了一个切入点时,也会截取所有@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 find建议看起来是这样的

@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 answer中所述,但您应该注意,在这种情况下,堆栈中低于建议执行的所有beforeFindOneByIdAdvice连接点也将被排除。因此,这可能会在未来给你带来一些意想不到的结果。

您可以在"面向方面编程-什么是';cflow';?"答案中看到cflow是如何工作的。

最新更新