Spring AOP exception handler - execute only first aspect in ordering

666 Views Asked by At

UPD I've updated code for Aspects to throw exception further

I have SpringBoot application, service class and I need to implement Exception Handler for my service (not MVC). The task is to log error and throw it further to the client.

I decided to use Aspect with @AfterThrowing advice. I'm gonna catch few exceptions (that extend RuntimeException) at AspectOne aspect. And for other cases I need to catch exceptions (that extend RuntimeException) at AspectTwo aspect.

So I did the following code:

public class MyOwnException extends RuntimeException {
}
@Aspect
@Order(0)
@Component
public class AspectOne {

@Pointcut("execution(* com.test.MyService.*(..))")
public void logException() {}

@AfterThrowing(pointcut="logException()", throwing="ex")
public void logException(MyOwnException ex) {
  System.out.println("MyOwnException has been thrown: " + ex.getMessage());
  throw ex;
}

}
@Aspect
@Order(1)
@Component
public class AspectTwo {

@Pointcut("execution(* com.test.MyService.*(..))")
public void logException() {}

@AfterThrowing(pointcut="logException()", throwing="ex")
public void logException(RuntimeException ex) {
  System.out.println("Some unknown exception has been thrown: " + ex);
  throw ex;
}

} 

The problem is that AspectTwo is executed in both cases for MyOwnException and other ancestors of RuntimeException. How can I limit AspectTwo to be executed only when AspectOne haven't caught the exception?

Seems like @Order annotation works not as I expected.

1

There are 1 best solutions below

0
On

How about a little hack to indicate an exception is already handled/adviced ?

Also note that , the order of execution to be AspectOne before AspectTwo here , the Order should be specified as 1 for AspectOne and 0 for AspectTwo.

From the reference documentation section : Advice Ordering

What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first "on the way in" (so, given two pieces of before advice, the one with highest precedence runs first). "On the way out" from a join point, the highest precedence advice runs last (so, given two pieces of after advice, the one with the highest precedence will run second).

Following code leverages the Throwable.addSuppressed() method to indicate an exception object is already handled/adviced.

--

Add an Exception class to be used as an indicator.

public class AlreadyAdvicedIndicator extends Exception {

    private static final long serialVersionUID = 1L;
    
    public AlreadyAdvicedIndicator(String message) {
        super(message);
    }

}

AspectOne with modified Order and logic to add a suppressed exception.

@Component
@Aspect
@Order(1)
public class AspectOne {

    public static final String ALREADY_ADVICED_MSG="Adviced with AspectOne";
    
    private static final AlreadyAdvicedIndicator alreadyAdviced = new AlreadyAdvicedIndicator(ALREADY_ADVICED_MSG);

    @Pointcut("execution(* com.test.MyService.*(..))")
    public void logException() {}

    @AfterThrowing(pointcut="logException()", throwing="ex")
    public void logException(MyOwnException ex) {
      System.out.println("MyOwnException has been thrown: " + ex.getMessage());
      ex.addSuppressed(alreadyAdviced);
    }
}

AspectTwo with modified Order and logic to check for already adviced.

@Component
@Aspect
@Order(0)
public class AspectTwo {

    @Pointcut("execution(* com.test.MyService.*(..))")
    public void logException() {
    }

    @AfterThrowing(pointcut = "logException()", throwing = "ex")
    public void logException(RuntimeException ex) {
        if (isAlreadyAdviced(ex)) {
            System.out.println("Already Adviced : Skipping");
        } else {
            System.out.println("RuntimeException has been thrown: " + ex.getMessage());
        }
    }

    private boolean isAlreadyAdviced(RuntimeException ex) {
        for(Throwable e : ex.getSuppressed()) {
            if(AspectOne.ALREADY_ADVICED_MSG.equals(e.getMessage())){
                return true;
            }
        }
        return false;
    }
}