@AfterThrowing 没有按预期工作

@AfterThrowing not work as expected

我想用AOP拦截所有服务层抛出的运行时异常,并作为域异常重新抛出。

@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
    }

}

这里的问题是,对于 DataAccessException 的子类,运行时会执行错误的方法。有一个优雅的解决方案吗?

Spring版本:4.2.4.RELEASE

P.S。具有大量 instanceof 的单个通用方法(从其他问题中读取)对我来说并不优雅 ;-)

谢谢 弗朗切斯科

我相信,您的期望是错误的(只有一种拦截方法会像方法重载一样匹配)。

但是虽然 RuntimeExceptionDataAccessException 的父级,但两种方法都被执行...

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

}

我的服务

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

    }
}

并且在日志中有:

DAE
RE - class test.MyService$MyDataAccessException
RE - class java.lang.NullPointerException

Maven 依赖项:

    <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.

当我尝试对 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
    }

}

日志更改为:

DAE
RE - class java.lang.IllegalArgumentException
RE - class java.lang.NullPointerException

修改为 Exception 我得到:

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
    }

}

日志是

DAE
RE - class java.lang.NullPointerException

我相信,"problem" 的解决方案是有两个方面而不是一个方面并定义顺序:

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
    }

}

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

}

使用 @Around 建议怎么样?您可以在其中简单地使用 type-safe try-catch,无需使用任何 instanceof 或反射。

这是我使用 AspectJ 而不是 Spring AOP 编译的一些示例代码,因为我不是 Spring 用户。反正切入点应该是一样的。

帮手类:

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

驱动程序申请(普通Java,无需使用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");
        }
    }
}

看点:

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

控制台日志:

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