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

时间:2016-10-02 21:36:12

标签: aop aspectj spring-aop spring-aspects

我正在使用:

  • Spring Framework 4.3.3
  • AspectJ 1.8.9

我有以下正常过程:

  • @Controller - > @Service - > @Repository

我有以下关于AOP的对:

  • PersonaServicePointcut
    • PersonaServiceAspect

方案如下:

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

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

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

中处理

实际上,save方法采用了相同的方法。因此,在执行之前,save方法其他 @Before@Around建议会再次调用findOneById方法。如果实体已经存在,则必须抛出异常,假设异常 B ,则必须在@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){}

再次:这三个建议使用或执行 findOneById方法。

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

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

我已创建此切入点以检查实体是否已存在,如果它不存在,则必须抛出类型为C的异常(对于经典404)。

通过findOneById@Before方法本身的@AroundfindOneById建议,似乎多余的执行 {/ 1}}方法。但我需要loggingaudit目的,并且还要创建 C 类型的例外。它必须由某些@ControllerAdvice

处理

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

我需要更改切入点声明以表示类似这样的内容:

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

Alpha的位置:

执行@Service的{​​{1}}方法的前/后建议, 从不,如果其他人已完成此项通话来自的建议 findOneById上课。

我已经尝试了多种使用PersonaServiceAspect!execution组合的方式,但没有结果。

甚至当我创建一个切入点拦截 所有 !within时方法及其各自唯一的@Service建议,并通过@Around参数,我能够检查调用的方法,然后执行相应的控制。但这种行为再次发生。

这意味着,通过以下方式:

ProceedingJoinPoint proceedingJoinPoint

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

有可能完成这种方法吗?怎么样?

感谢。

2 个答案:

答案 0 :(得分:1)

您可以使用

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

在您的示例中,您希望排除执行在您自己的建议的控制流中的findOneById的执行。如果您的建议适用于切入点表达式

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

你可以用以下内容排除:

!cflow(combinedPointcut())

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

!cflow(adviceexecution())

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

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

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

答案 1 :(得分:1)

如果我理解正确,您希望在您的建议中不调用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
    }
}

另一种方法是使用@NándorElődFekete回答中描述的cflow切入点,但是你应该注意,在这种情况下,堆栈中建议执行的所有beforeFindOneByIdAdvice连接点也将被排除。所以这对你来说会有一些意想不到的结果。

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