0

in my spring boot application, I have been using one external commons library for handling exceptions. The external library has an aspect defined for the same something like below:

@Aspect
@Order(0)
public class InternalExceptionAspect {

  public InternalExceptionAspect() {
  }

  @Pointcut("@within(org.springframework.stereotype.Service)")
  public void applicationServicePointcut() {
  }

  @AfterThrowing(
    pointcut = "applicationServicePointcut()",
    throwing = "e"
  )
  public void translate(JoinPoint joinPoint, Throwable e) {
    String resourceId = this.getResourceId();
    if (e instanceof BadInputException) {
      BadInputException inputException = (BadInputException)e;
      throw new BadRequestAlertException(inputException.getErrorCode().getDefaultMessage(), inputException.getMessage(), inputException.getErrorCode().getHttpStatusCode(), resourceId, inputException.getErrorCode().getCode());
    } else if (!(e instanceof BadServerStateException) && !(e instanceof InternalException)) {
      String message;
      if (e instanceof JDBCException) {
        ...
        throw new BadServerStateException(message, resourceId, "20");
      } else {
        ...
        throw new BadServerStateException(message, resourceId, "10");
      }
    } else {
      InternalException serverStateException = (InternalException)e;
      throw new BadServerStateException(serverStateException.getErrorCode().getDefaultMessage(), serverStateException.getMessage(), resourceId, serverStateException.getErrorCode().getHttpStatusCode(), serverStateException.getErrorCode().getCode(), serverStateException.getErrorCode().getErrorType().name());
    }
  }

  String getResourceId() {
    RequestHeaders requestHeaders = RequestResponseContext.getRequestHeaders();
    return requestHeaders.getResourceId();
  }
}

Here I would like to introduce another else if block in order to handle DuplicateKeyException for my application.

The problem is, the above code, being part of the commons library is being used by multiple other applications. But, I would like to do the change to be applied only in my application.

I have been thinking to inherit the Aspect class something like below, inside my application:

 @Aspect
 @Order(0)
 public class MyInternalExceptionAspect extends InternalExceptionAspect {
  public MyInternalExceptionAspect() {
  }

  @Pointcut("@within(org.springframework.stereotype.Service)")
  public void applicationServicePointcut() {
  }

  @AfterThrowing(
    pointcut = "applicationServicePointcut()",
    throwing = "e"
  )
  public void translate(JoinPoint joinPoint, Throwable e) { 
        if(e instanceof DuplicateKeyException) {
            ...
         }
         super.translate(joinpoint, e);
     }
}

But, I am not sure, if this is the correct approach to do this. Could anyone please help here regarding what would be the best approach to achieve this? Thanks.

1
  • Did you try? Does it work? If it does not work as expected, what happens instead? Furthermore, your aspects do not have @Component annotations. Please show and explain how they are being wired. XML config? @Bean factory methods? Commented Nov 6, 2021 at 9:03

1 Answer 1

1

You cannot extend a concrete aspect using class MyInternalExceptionAspect extends InternalExceptionAspect. It will cause an exception in Spring AOP:

...AopConfigException:
  [...MyInternalExceptionAspect] cannot extend concrete aspect
  [...InternalExceptionAspect]

Only abstract aspects are meant to be extended.

But you can simply create a new aspect without inheritance and make sure that it has a lower priority than the original aspect.

Why lower priority?

  • Acording to the @Order javadoc, "lower values have higher priority".
  • You want your own aspect's @AfterReturning advice to kick in before the original aspect possibly transforms the exception of interest into something else, before you have a chance to handle it. But according to the Spring manual: "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).".
  • Therefore, your own aspect should have @Order(1), giving it lower priority than the original aspect, but making the @AfterThrowing advide run before the original one. Sorry for the reverse logic, even though it makes sense. You just need to be aware of it.

Here is an MCVE, simulating your situation in a simplified way:

package de.scrum_master.spring.q69862121;

import org.springframework.stereotype.Service;

@Service
public class MyService {
  public void doSomething(Throwable throwable) throws Throwable {
    if (throwable != null)
      throw throwable;
  }
}
package de.scrum_master.spring.q69862121;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;

@SpringBootApplication
@Configuration
public class DemoApplication {
  private static final Logger log = LoggerFactory.getLogger(DemoApplication.class.getName());

  public static void main(String[] args) throws Throwable {
    try (ConfigurableApplicationContext appContext = SpringApplication.run(DemoApplication.class, args)) {
      doStuff(appContext);
    }
  }

  private static void doStuff(ConfigurableApplicationContext appContext) {
    MyService myService = appContext.getBean(MyService.class);
    List<Throwable> throwables = Arrays.asList(
      null,                                   // No exception -> no aspect should kick in
      new Exception("oops"),                  // Not covered by any aspects -> no translation
      new IllegalArgumentException("uh-oh"),  // Original aspect translates to RuntimeException
      new NullPointerException("null"),       // Custom aspect translates to RuntimeException
      new ArithmeticException("argh")         // Custom aspect translates to IllegalArgumentException,
                                              // then original aspect translates to RuntimeException
    );

    for (Throwable originalThrowable : throwables) {
      try {
        myService.doSomething(originalThrowable);
      }
      catch (Throwable translatedThrowable) {
        log.info(translatedThrowable.toString());
      }
    }
  }
}

As you can see, the application calls the service, the first time with null, not causing any exception, then with several types of exceptions the aspects are meant to either ignore or translate.

package de.scrum_master.spring.q69862121;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(0)
public class InternalExceptionAspect {
  private static final Logger log = LoggerFactory.getLogger(InternalExceptionAspect.class.getName());

  @AfterThrowing(pointcut = "@within(org.springframework.stereotype.Service)", throwing = "e")
  public void translate(JoinPoint joinPoint, Throwable e) {
    log.info(joinPoint + "  -> " + e);
    if (e instanceof IllegalArgumentException)
      throw new RuntimeException("Transformed by InternalExceptionAspect", e);
  }
}
package de.scrum_master.spring.q69862121;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(1)
public class MyInternalExceptionAspect {
  private static final Logger log = LoggerFactory.getLogger(MyInternalExceptionAspect.class.getName());

  @AfterThrowing(pointcut = "@within(org.springframework.stereotype.Service)", throwing = "e")
  public void translate(JoinPoint joinPoint, Throwable e) {
    log.info(joinPoint + "  -> " + e);
    if (e instanceof NullPointerException)
      throw new RuntimeException("Transformed by MyInternalExceptionAspect", e);
    if (e instanceof ArithmeticException)
      throw new IllegalArgumentException("Transformed by MyInternalExceptionAspect", e);
  }
}

The console log proves that everything works as expected, also with regard to invocation order:

d.s.s.q.MyInternalExceptionAspect        : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable))  -> java.lang.Exception: oops
d.s.s.q69862121.InternalExceptionAspect  : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable))  -> java.lang.Exception: oops
d.s.spring.q69862121.DemoApplication     : java.lang.Exception: oops
d.s.s.q.MyInternalExceptionAspect        : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable))  -> java.lang.IllegalArgumentException: uh-oh
d.s.s.q69862121.InternalExceptionAspect  : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable))  -> java.lang.IllegalArgumentException: uh-oh
d.s.spring.q69862121.DemoApplication     : java.lang.RuntimeException: Transformed by InternalExceptionAspect
d.s.s.q.MyInternalExceptionAspect        : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable))  -> java.lang.NullPointerException: null
d.s.s.q69862121.InternalExceptionAspect  : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable))  -> java.lang.RuntimeException: Transformed by MyInternalExceptionAspect
d.s.spring.q69862121.DemoApplication     : java.lang.RuntimeException: Transformed by MyInternalExceptionAspect
d.s.s.q.MyInternalExceptionAspect        : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable))  -> java.lang.ArithmeticException: argh
d.s.s.q69862121.InternalExceptionAspect  : execution(void de.scrum_master.spring.q69862121.MyService.doSomething(Throwable))  -> java.lang.IllegalArgumentException: Transformed by MyInternalExceptionAspect
d.s.spring.q69862121.DemoApplication     : java.lang.RuntimeException: Transformed by InternalExceptionAspect
Sign up to request clarification or add additional context in comments.

6 Comments

Sorry, I had some copy & paste errors in my example code. I just noticed and fixed it.
thanks a lot for the detailed explanation. One problem I am still facing with this approach. The exception is indeed intercepted with the new aspect introduced, but I was hoping to change the exception type before it reaches the original aspect. In the original aspect, this exception originally thrown from the application was getting treated as a Bad Server exception, which I wanted to treat as a Bad input exception, this conversion I have already done in my aspect.
I do not understand your question. My guess is that you want your aspect to catch exception type A, converting it to B, then the original aspect handles B, converting it to C. Correct? I just extended the sample code to log more info (also the incoming exception) and one more exception translation case (ArithmeticExceptionIllegalArgumentExceptionRuntimeException). Is this what you want?
Update: I also reorganised method DemoApplication.doStuff a bit in order to use a loop for calling the helper method with the original exception and logging the translated one. I added comments for each case, too.
Apologies for the confusion. Exactly. I wanted my aspect to catch exception type A, converting it to B, then the original aspect handles B.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.