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
@Componentannotations. Please show and explain how they are being wired. XML config?@Beanfactory methods?