3

I am working with:

  • Spring Framework 4.3.3
  • AspectJ 1.8.9

I have the following normal process:

  • @Controller -> @Service -> @Repository

I have the following pair about AOP:

  • PersonaServicePointcut
    • PersonaServiceAspect

The scenario is the following:

The @Service class has some methods such as: delete, save, update and findOneById. They are declared together within the same class.

For the methods such as delete and update through AOP I am using a @Before or @Around advice to call the findOneById method.

The reason is that does not make sense execute the delete or update methods (consider Rest scenario) if the entity does not exist. Therefore through that advice an exception must be throw, lets say Exception A, it must be handled in a @ControllerAdvice

Practically the same approach for the save method has been applied. Therefore before to execute the save method other @Before or @Around advice is executed calling the findOneById method again. If the entity already exists an exception must be thrown, lets say Exception B, it must be handled in a @ControllerAdvice

Observe I have 3 points/3advices that use the findOneById method. It to check if exists an entity or not.

For example:

    @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){}

Again: these 3 advices use or execute the findOneById method.

The problem is when I add a new pointcut such as:

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

I have created this pointcut to check if an entity already exists or not, if it does not exist it must throw an exception of type C (it for the classic 404).

Seems redundant execute the findOneById method through a @Before or @Around advice for the findOneById method itself. But I need this to logging and audit purposes and to create the exception of type C too. It must be handle by some @ControllerAdvice

The problem is when the others advices for the delete/update/save methods are executed (remember they call and execute the findOneById method too) my findOneByIdPointcut is executed unnecessarily.

I need change the pointcut declaration to indicate something like this:

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

Where Alpha is:

execute the before/around advice for the @Service's findOneById method, but never if its call has been done from the others advices from the
PersonaServiceAspect class.

I've tried many ways with !execution and !within combinations, but no results.

Even when I have created just one Pointcut that intercepts all the @Service's methods with its respective unique @Around advice, and through the ProceedingJoinPoint proceedingJoinPoint parameter I am able to check what method has been called and then do the respective controls. But again this behaviour happens.

It means, through the following:

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

where anyMethodPointcut is execution(* mypackage.PersonaServiceImpl.*(..))

Is possible accomplish this approach? How?

Thanks.

Manuel Jordan
  • 15,253
  • 21
  • 95
  • 158

2 Answers2

2

You can exclude pointcuts within the control flow of a pointcut expression with

!cflow(<pointcut>)

In your example, you want to exclude those executions of the findOneById where the execution is inside the control flow of your own advice. If your advice is applied to the pointcut expression

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

you can exclude that with:

!cflow(combinedPointcut())

or to simply exclude all control flows from within the control flow of all advice executions, you could use:

!cflow(adviceexecution())

Your around find advice would look like this, based on the combined pointcut expression:

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

Note that you can't combine the pointcut expressions in your example because they're binding pointcut arguments. You need to remove the && args(...) parts and move them to the advice pointcut expressions directly. You can't combine binding pointcut expressions like args(paramName) with the || (or) operator so you'll need to create separate advices for those cases. You can still delegate most of the work from those advices to a single method if you want to. See an example of this kind of delegation here.

Community
  • 1
  • 1
Nándor Előd Fekete
  • 6,988
  • 1
  • 22
  • 47
  • Thanks a lot by the detailed explanation. Let me do the testing by my side. About the `binding pointcut arguments` I had some problems trying to mixing. So you are correct. Therefore, the following seems impossible http://stackoverflow.com/questions/39730232/aop-combining-two-pointcuts-but-with-parameters-if-is-possible – Manuel Jordan Oct 03 '16 at 13:06
  • 1
    Yes, you would get the `inconsistent binding` error message. But it's very easy to work around it, an example is show in my answer [here](http://stackoverflow.com/a/34426950/2699901). In my answer, you'll see examples of binding pointcut expressions for `@this()` and `@annotation()`, but the same applies to any other binding pointcut expression, `args()` in your case. – Nándor Előd Fekete Oct 03 '16 at 13:23
  • I am getting `org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException:` it because `cflow` is not supported by `Spring` such as is indicate here: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-pointcuts-designators. Checking right now: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-aj-ltw – Manuel Jordan Oct 03 '16 at 14:14
  • Is there other way to accomplish this? My `@AspectJ` use `@Transactional`. So even If I configure it about `Load-time weaving` it is not handle by Spring. Even more when my class uses `@Autowired` – Manuel Jordan Oct 03 '16 at 14:42
  • I was able to resolve this without `cflow`. I did a refactor (there the true problem). But just curious if my previous comment is possible or not. – Manuel Jordan Oct 03 '16 at 16:10
  • 1
    You need to decide if you want to stick with Spring AOP (reduced functionality and other limitations, i.e. it's proxy based) or go with full AspectJ. I had great success with AspectJ compile time weaving in my projects, you just have to configure your build accordingly. Load-time weaving should work too. It's quite easy to integrate AspectJ with Spring so this shouldn't be an issue. – Nándor Előd Fekete Oct 03 '16 at 22:44
  • Thanks by the update. I will do a deeper research. Not sure if `Load-time weaving should work too` would work with `@Transactional` – Manuel Jordan Oct 03 '16 at 23:19
  • 1
    Yes, it would. I used spring transaction management with both load-time weaving and compile time weaving. – Nándor Előd Fekete Oct 04 '16 at 02:05
  • Thanks a lot by the confirmation. Just curious if you have a tutorial mixing these two approaches but through Spring `Java Config`. Seems mandatory until now use `aop.xml` within the `META-INF` – Manuel Jordan Oct 05 '16 at 18:46
  • 1
    I can't come up with a tutorial right away, but the important thing when enabling Spring Transaction management with AspectJ is to use the `mode` attribute of the [`@EnableTransactionManagement`](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/annotation/EnableTransactionManagement.html) annotation with the value `ASPECTJ`. Use of `aop.xml` is not mandatory AFAIK, the `spring-aspects` module, which contains the AspectJ transaction management code contains one though for it's own aspects. – Nándor Előd Fekete Oct 05 '16 at 19:16
  • Thanks, I will do a research. About the tutorial I mean if you know a good tutorial, through Google the results are from 2008 and 2010. Best Regards! – Manuel Jordan Oct 05 '16 at 23:01
1

If I understand correctly you want the findOneByIdPointcut is not invoked inside you advice but should triggered in all other cases.

One way to do this is to use call in combination with within pointcuts. call will move join point up to caller method and within will exclude advice from target join points.

So your aspect code may look like this:

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
    }
}

Another way is to use cflow pointcut like described in @Nándor Előd Fekete answer, but you should note that in this case all beforeFindOneByIdAdvice join points below advice execution in the stack will be excluded too. So this can have some unexpected results for you in the future.

You can see how cflow works in "Aspect oriented programming - what is 'cflow'?" answer.

Community
  • 1
  • 1
Sergey Bespalov
  • 1,746
  • 1
  • 12
  • 29
  • Thanks again by your valuable support. I already have resolved this doing a refactoring. The trick is that the call of the `findOneById` must be do using the dependency of the beneath layer. It means through the `@Repository`. Therefore the advices about `save, delete, update` do not affect indirectly and unnecessarily the `find` advice. That was my mistake. The point is, the `Aspect` intercepts any call to a `@Service` method. From there each `Aspect`'s advice calls or execute the `findOneById` method through the `@Repository` dependency. – Manuel Jordan Oct 04 '16 at 12:52