@AfterThrowing не работает должным образом
Я хочу использовать 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
PS Один общий метод (читай из других вопросов) с большим количеством instanceof для меня не изящен;-)
Спасибо Франческо
2 ответа
Я полагаю, что ваше ожидание неверно (только один метод перехвата будет соответствовать так же, как и при перегрузке метода).
Но пока RuntimeException
является родителем DataAccessException
оба метода выполняются...
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);
}
}
}
и в журнале есть:
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>
Когда два совета, определенных в одном и том же аспекте, должны выполняться в одной и той же точке соединения, порядок не определен (поскольку нет способа получить порядок объявления посредством отражения для классов, скомпилированных с помощью javac). Подумайте о том, чтобы объединить такие методы рекомендаций в один метод рекомендаций для каждой точки соединения в каждом классе аспектов или реорганизовать фрагменты рекомендаций в отдельные классы аспектов, которые можно упорядочить на уровне аспектов.
Когда я попробовал следующую модификацию 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
Я полагаю, что решение вашей "проблемы" состоит в том, чтобы иметь два аспекта вместо одного и определить порядок:
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
совет? Вы можете просто использовать безопасный тип 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