Search code examples
javaspringaopaspectjspring-aop

@AfterThrowing advice is applied twice


Can we handle exception like try-catch construction with Spring AOP?

When some exception thrown and correspondent aspect contains two advices, with exception class and its superclass,

@AfterThrowing(pointcut = "bean(advisedService)", throwing = "e")
void afterThrowingException(RuntimeException e) {}

@AfterThrowing(pointcut = "bean(advisedService)", throwing = "e")
void afterThrowingException(Exception e) {}

they both are applying.

While in plain catch(…){} we can achieve the desired result by ordering from subclass to superclass (from particular to general):

try {
    advisedService.doSomething();
}
catch (RuntimeException e) {}
catch (Exception e) {}

and process handling ex of specific type just once✔️


GitHub //with demo test


Solution

  • The aspect behaves exactly as expected, the way you designed it. Try not to compare apples with pears. In an @AfterThrowing advice, you cannot catch/handle exceptions, only do something on top of the exception being thrown, e.g. log them the way you do. Both of your advice methods will fire, because both pointcuts match. So far, so normal.

    If you want different behaviour, just change the code. How about this?

    @AfterThrowing(pointcut = "bean(advisedService)", throwing = "e")
    void afterThrowingException(Exception e) {
      System.out.printf(
        "AdvisedServiceAspect %s exception: %s%n",
        e instanceof RuntimeException ? "runtime" : "checked",
        e.getClass().getSimpleName()
      );
    }
    

    Or if you really want to handle exceptions and not just log them, use an @Around advice (made-up example):

    @Around("bean(advisedService)")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
      try {
        return joinPoint.proceed();
      }
      catch (RuntimeException e) {
        System.out.println("Runtime exception caught, returning null");
        return null;
      }
      catch (Exception e) {
        throw new RuntimeException("Wrap checked exception in runtime exception", e);
      }
      // Do not handle Throwable, i.e. let it escalate
    }