Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@AfterThrowing not work as expected

Tags:

java

spring

aop

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

like image 268
Francesco Avatar asked Jan 05 '16 14:01

Francesco


People also ask

Which exception is thrown if an attempt is made to use a AspectJ designator which is not supported in Spring AOP?

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.

Is after advice executed even if the Joinpoint exist after throwing exception?

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.

What is an after throwing advice Java?

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.


2 Answers

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
like image 200
kriegaex Avatar answered Oct 23 '22 01:10

kriegaex


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");
    }

}
like image 24
Betlista Avatar answered Oct 22 '22 23:10

Betlista