0

I use in my Spring boot project aspect that is fired on every public method in annotated class:

@Aspect
@Component
public class DeletedAwareAspect {
    @Before("@within(com.example.DeleteAware)")
    public void aroundExecution(JoinPoint pjp) throws Throwable {
        //... some logic before
    }

    @After("@within(com.example.DeleteAware)")
    public void cleanUp(JoinPoint pjp) throws Throwable {
       //... some logic after
    }
}

Usage of that aspect is below:

@DeleteAware
@Service
public class MyService {
    public void foo() {}
}
@DeleteAware
@Service
public class MyAnotherService {
    @Autowired
    private MyService service;

    public void anotherFoo() {}

    public void VERY_IMPORTANT_METHOD() {
        service.foo();
    }
}

MyService.foo() and MyAnotherService.anotherFoo() works as expected. But here is the problem - if method wrapped by aspect is called by another aspected method (like VERY_IMPORTANT_METHOD()), I dont want to fire aspect twice, but only once. How to check from Aspect whether method is called inside another aspected method?

kriegaex
  • 63,017
  • 15
  • 111
  • 202
Cichy
  • 1,319
  • 3
  • 20
  • 36
  • Just an idea, I’m currently on my phone therefor I can not try it out: `joinpoint.getStaticPart()` which contains static information. Maybe it is possible to check whether the joinpoint comes from the same annotated class. I hope you understand what I mean. For better performance you could also replace the JoinPoint from the method signature with the StaticPart type. Should be something like `Joinpoint.StaticPart` – Daniel Rafael Wosch Jan 27 '21 at 23:02
  • 2
    Without examining the call stack it will not be possible to identify the caller method. Another way to tackle this problem would be to exclude the `VERY_IMPORTANT_METHOD()` from the scope of the advice methods. example `@within(com.example.DeleteAware) && !@annotation(ExcludeMethod)` , where `ExcludeMethod` would be a method level annotation. Code modification will be required to identify such methods . Another approach would be to use threadlocal to flag that it is already adviced and examine the same in the advice before executing the logic . – R.G Jan 28 '21 at 02:30
  • 1
    Have you considered switching to native AspectJ and using `cflow()` or `cflowbelow()`? P.S.: It looks like you want to do something similar to what `@Transactional` does. As you didn't explain what your aspect does, it is impossible to tell if you could use that annotation instead. I also cannot advise in a concrete way about how to re-implement your aspect without native AspectJ in order to not delete resources twice, be it by using the mentioned `ThreadLocal` or by another means. So please present an [MCVE](https://stackoverflow.com/help/mcve) if you wish to discuss this any further. – kriegaex Jan 30 '21 at 05:27
  • @kriegaex, yes I want to achieve very similare functionality like 'Transactional' (required propagation). To be more specific, I need to turn on JPA Filter on Session in 'Before' aspect and turn it off in 'After' section. It does not work if I inject service into another service with that annotation, because once inner service ends, it turns my Filter off, and later in outer service filter is already turned off and my functionality doesn't work. – Cichy Feb 01 '21 at 08:29
  • Yes, in this case you need to do some manual book-keeping. Question: Is everything between the outermost 'before' and its counterpart 'after' advices in a single thread? That would greatly simplify things. Your choice for possible approaches strongly depends on your answer to this question. – kriegaex Feb 01 '21 at 10:13
  • @kriegaex - yes, this is single-thread safe. – Cichy Feb 02 '21 at 10:32

1 Answers1

1

Like I said, if you would switch to native AspectJ you could use percflow() aspect instantiation and/or cflow() pointcuts, but I will not explain that here in detail because you are looking for a Spring AOP solution. Like R.G said, it is actually quite simple: Use a thread-local counter for the aspect nesting level and only do something (e.g. deleting something in the after advice) if the counter is zero.

@Aspect
@Component
public class DeletedAwareAspect {
  private ThreadLocal<Integer> nestingLevel = ThreadLocal.withInitial(() -> 0);

  @Before("@within(com.example.DeleteAware)")
  public void aroundExecution(JoinPoint pjp) {
    int level = nestingLevel.get() + 1;
    nestingLevel.set(level);
    System.out.println("BEFORE " + pjp + " -> " + level);
  }

  @After("@within(com.example.DeleteAware)")
  public void cleanUp(JoinPoint pjp) {
    int level = nestingLevel.get() - 1;
    nestingLevel.set(level);
    System.out.println("AFTER " + pjp + " -> " + level);
    if (level == 0)
      System.out.println("Deleting something");
  }
}
kriegaex
  • 63,017
  • 15
  • 111
  • 202