Spring Boot 2 After aspect find success or failure

Viewed 573

I am working on Spring Boot 2 to create a microservice. I have a requirement to create an After aspect to execute some piece of code.

@Aspect
@Component
public class FinallyAspect {

    @Pointcut("@annotation(finallyEvent)")
    public void runFinallyMethod(FinallyEvent finallyEvent) {}

    @After("runFinallyMethod(FinallyEvent finallyEvent)")
    public void finallyMethod(JoinPoint joinPoint, FinallyEvent finallyEvent) throws Throwable { 
       // ...
    }
}

Is it possible to get inside finallyMethod whether an exception has occurred or the method returned successfully? I can do it with @AfterReturning and @AfterThrowing annotation, but if there is a way to check if the method has ended in error or success then I can check it in a single function.

2 Answers

It is not possible with After-advice to access whether the method returned successfully or with an exception. There are alternatives...

What you want can be manually implemented with a single method using the Around-advice, the most general kind of advice. It is recommended that you use the least powerful advice type that can implement the required behaviour (source). I do not recommend this approach as it can be error-prone in terms of exception handling if implemented the wrong way. For example, if you put your success-code in the try-block, exceptions thrown by this success-code are also caught by the same catch-block as is used for the failure-code. Also, you need to make sure to re-throw the exception and to return the return value of joinPoint.proceed().

This is how could do this properly if you wanted to:

@Around(value = "runFinallyMethod(finallyEvent)", argNames = "joinPoint,finallyEvent")
public Object finallyMethod(ProceedingJoinPoint joinPoint, FinallyEvent finallyEvent) throws Throwable {
    final Object res;
    try {
        res = joinPoint.proceed();
    } catch (Throwable e) {
        // code in case of failure
        throw e;
    }
    // code in case of success
    return res;
}

b) Clean solution with private method

In this case, I suggest to use AfterReturning-advice and AfterThrowing-advice and then call a private method with a parameter indicating success/error. This is much more readable, does not have the drawbacks of the Around-advice but uses a bit more code.

A boolean (success) is needed

@AfterReturning(value = "runFinallyMethod(finallyEvent)", argNames = "joinPoint,finallyEvent")
public void finallyMethodReturning(JoinPoint joinPoint, FinallyEvent finallyEvent) throws Throwable {
    finallyMethod(joinPoint, finallyEvent, true);
}

@AfterThrowing(value = "runFinallyMethod(finallyEvent)", argNames = "joinPoint,finallyEvent")
public void finallyMethodThrowing(JoinPoint joinPoint, FinallyEvent finallyEvent) throws Throwable {
    finallyMethod(joinPoint, finallyEvent, false);
}

private void finallyMethod(JoinPoint joinPoint, FinallyEvent finallyEvent, boolean success) throws Throwable {
    if (success) {
        // code in case of success
    } else {
        // code in case of failure
    }
}

The Throwable is needed

@AfterReturning(value = "runFinallyMethod(finallyEvent)", argNames = "joinPoint,finallyEvent")
public void finallyMethodReturning(JoinPoint joinPoint, FinallyEvent finallyEvent) throws Throwable {
    finallyMethod(joinPoint, finallyEvent, null);
}


@AfterThrowing(value = "runFinallyMethod(finallyEvent)", throwing = "t", argNames = "joinPoint,finallyEvent,t")
public void finallyMethodThrowing(JoinPoint joinPoint, FinallyEvent finallyEvent, Throwable t) throws Throwable {
    finallyMethod(joinPoint, finallyEvent, t);
}

private void finallyMethod(JoinPoint joinPoint, FinallyEvent finallyEvent, Throwable t) throws Throwable {
    if (t == null) {
        // code in case of success
    } else {
        // code in case of failure
    }
}

I don't think you will be able to implement this using @After as this annotation can only give you the JoinPoint in context, which has no information about return values.

If you want to handle everything within the same method I think the only alternative is to implement this using @Around, where you can do something before and after a method execution. Your implementation could be:

@Around("runFinallyMethod(FinallyEvent finallyEvent)")
public Object finallyMethod(ProceedingJoinPoint jp, FinallyEvent finallyEvent) throws Throwable { 
   try {
     Object result = jp.proceed();
     // do nice stuff with result
     return result;
   } catch(Throwable throwable) {
     // do nice stuff with the exception;
     throw throwable;
   }
}
Related