Home > database >  Extending a exception aspect in order to have application specific implementation
Extending a exception aspect in order to have application specific implementation

Time:11-09

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.

CodePudding user response:

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
  • Related