I want to use AOP to intercept all runtime exceptions thrown in service layer and rethrow as domain exceptions.
@Aspect
@Component
public class ExceptionWrapperInterceptor {
@Pointcut("within(*.service.*)")
public void onlyServiceClasses() {}
@AfterThrowing(pointcut = "onlyServiceClasses()", throwing = "ex")
public void intercept(DataAccessException ex) throws Exception {
//throw DatabaseException
}
@AfterThrowing(pointcut = "onlyServiceClasses()", throwing = "ex")
public void intercept(RuntimeException ex) throws Exception {
//throw ServiceException
}
}
The problem here is that, with a subclass of DataAccessException, the runtime execute the wrong method. There is an elegant solution to this?
Spring Version: 4.2.4.RELEASE
P.S. A single generic method (read from other questions) with a lot of instanceof is not elegant for me ;-)
Thanks Francesco
AspectJ @AfterThrowing Annotation Usage AspectJ @AfterThrowing advice is executed after a join point does not complete normally and end up throwing an exception. @AfterThrowing ( "execution(* com.
After throwing advice. After throwing advice runs when a matched method execution exits by throwing an exception. It is declared using the @AfterThrowing annotation: import org.
After-throwing is an advice type which ensures that an advice runs after the method execution, only if the method exits by throwing an exception. Following is the syntax of after-throwing advice.
How about using an @Around
advice? You can simply use type-safe try-catch
therein, no need to use any instanceof
or reflection.
Here is some sample code which I compiled using AspectJ instead of Spring AOP because I am not a Spring user. The pointcut should be the same anyway.
Helper classes:
package de.scrum_master.service;
public class DatabaseException extends RuntimeException {
public DatabaseException(Throwable arg0) {
super(arg0);
}
}
package de.scrum_master.service;
public class ServiceException extends RuntimeException {
public ServiceException(Throwable arg0) {
super(arg0);
}
}
Driver application (plain Java, no need to use Spring):
package de.scrum_master.service;
import java.util.Random;
import org.springframework.jdbc.datasource.init.ScriptParseException;
public class Application {
private static final Random RANDOM = new Random();
public static void main(String[] args) {
Application application = new Application();
for (int i = 0; i < 10; i++) {
try {
application.doSomething();
}
catch (Exception e) {
System.out.println(e);
}
}
}
public void doSomething() {
switch (RANDOM.nextInt(3)) {
case 1: throw new ScriptParseException("uh-oh", null);
case 2: throw new IllegalArgumentException("WTF");
default: System.out.println("doing something");
}
}
}
Aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Component;
import de.scrum_master.service.DatabaseException;
import de.scrum_master.service.ServiceException;
@Aspect
@Component
public class ExceptionWrapperInterceptor {
@Pointcut("within(*..service..*) && execution(* *(..))")
public void onlyServiceClasses() {}
@Around("onlyServiceClasses()")
public Object intercept(ProceedingJoinPoint thisJoinPoint) {
try {
return thisJoinPoint.proceed();
}
catch (DataAccessException dae) {
throw new DatabaseException(dae);
}
catch (RuntimeException re) {
throw new ServiceException(re);
}
}
}
Console log:
doing something
de.scrum_master.service.DatabaseException: org.springframework.jdbc.datasource.init.ScriptParseException: Failed to parse SQL script from resource [<unknown>]: uh-oh
doing something
de.scrum_master.service.DatabaseException: org.springframework.jdbc.datasource.init.ScriptParseException: Failed to parse SQL script from resource [<unknown>]: uh-oh
doing something
de.scrum_master.service.ServiceException: java.lang.IllegalArgumentException: WTF
de.scrum_master.service.ServiceException: java.lang.IllegalArgumentException: WTF
de.scrum_master.service.ServiceException: java.lang.IllegalArgumentException: WTF
de.scrum_master.service.ServiceException: java.lang.IllegalArgumentException: WTF
doing something
I believe, that your expectation is wrong (that only one intercept method will match similarly as for method overloading).
But while RuntimeException
is parent of DataAccessException
both methods are executed...
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<context:component-scan base-package="test" />
<aop:aspectj-autoproxy />
</beans>
AopTest
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring.xml");
MyService ms = ac.getBean(MyService.class);
try {
ms.throw1();
} catch (Exception e) {
// e.printStackTrace();
}
try {
ms.throw2();
} catch (Exception e) {
// e.printStackTrace();
}
}
}
MyAspect
package test;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
@AfterThrowing(pointcut = "execution(public * *(..))", throwing = "ex")
public void intercept(DataAccessException ex) throws Exception {
//throw DatabaseException
System.out.println("DAE");
}
@AfterThrowing(pointcut = "execution(public * *(..))", throwing = "ex")
public void intercept(RuntimeException ex) throws Exception {
//throw ServiceException
System.out.println("RE - " + ex.getClass());
}
}
MyService
package test;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
@Service
public class MyService {
public void throw1() throws DataAccessException {
throw new MyDataAccessException("test");
}
public void throw2() {
throw new NullPointerException();
}
static class MyDataAccessException extends DataAccessException {
public MyDataAccessException(String msg) {
super(msg);
}
}
}
and in log there is:
DAE
RE - class test.MyService$MyDataAccessException
RE - class java.lang.NullPointerException
Maven dependencies:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
From Spring documentation:
When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the declaration order via reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each aspect class, or refactor the pieces of advice into separate aspect classes - which can be ordered at the aspect level.
When I tried following modification of MyAspect
:
package test;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
@AfterThrowing(pointcut = "execution(public * *(..))", throwing = "ex")
public void intercept(DataAccessException ex) throws Exception {
//throw DatabaseException
System.out.println("DAE");
throw new IllegalArgumentException("DAE"); // added
}
@AfterThrowing(pointcut = "execution(public * *(..))", throwing = "ex")
public void intercept(RuntimeException ex) throws Exception {
//throw ServiceException
System.out.println("RE - " + ex.getClass());
throw new IllegalArgumentException("RE"); // added
}
}
log changed to:
DAE
RE - class java.lang.IllegalArgumentException
RE - class java.lang.NullPointerException
and when modified to Exception
I got:
package test;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
@AfterThrowing(pointcut = "execution(public * *(..))", throwing = "ex")
public void intercept(DataAccessException ex) throws Exception {
//throw DatabaseException
System.out.println("DAE");
throw new Exception("DAE2"); // changed
}
@AfterThrowing(pointcut = "execution(public * *(..))", throwing = "ex")
public void intercept(RuntimeException ex) throws Exception {
//throw ServiceException
System.out.println("RE - " + ex.getClass());
throw new Exception("RE2"); // changed
}
}
the log was
DAE
RE - class java.lang.NullPointerException
I believe, that solution to your "problem" is to have two Aspects instead of one and define the ordering:
package test;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.Ordered;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DaeAspect implements Ordered {
public int getOrder() {
return 200;
}
@AfterThrowing(pointcut = "execution(public * *(..))", throwing = "ex")
public void intercept(DataAccessException ex) throws Exception {
//throw DatabaseException
System.out.println("DAE");
throw new IllegalAccessException("DAE2"); // based on my testing, this stops second aspect to apply
}
}
and
package test;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.Ordered;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ReAspect implements Ordered {
public int getOrder() {
return 100;
}
@AfterThrowing(pointcut = "execution(public * *(..))", throwing = "ex")
public void intercept(RuntimeException ex) throws Exception {
//throw ServiceException
System.out.println("RE - " + ex.getClass());
throw new IllegalAccessException("RE2");
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With