2016-01-05 20 views
5

Ich möchte AOP verwenden, um alle in Service-Layer geworfenen Laufzeitausnahmen abzufangen und als Domain-Ausnahmen erneut auszuführen.@AfterThrowing funktioniert nicht wie erwartet

Das Problem hier ist, dass die Laufzeit mit einer Unterklasse von DataAccessException die falsche Methode ausführen. Es gibt eine elegante Lösung dafür?

Frühling Version: 4.2.4.RELEASE

P. S. Eine einzelne generische Methode (liest von anderen Fragen) mit vielen instanceof ist für mich nicht elegant ;-)

Dank Franc

+0

Mit "falscher Methode" Sie meinten, dass beide Methoden ausgeführt werden, nicht wahr? – Betlista

+0

Nein. Nur die Methode 'intercept (RuntimeException)'. – Francesco

+0

Können Sie meine Antwort überprüfen und teilen, was in Ihrem Fall anders ist? Sie haben Spring Version, Definition von 'onlyServiceClasses' und andere Details nicht geteilt ... – Betlista

Antwort

3

Ich glaube, dass Ihre Erwartung falsch ist (dass nur ein Schnittverfahren wird Übereinstimmen ähnlich wie beim Überladen von Methoden).

Aber während RuntimeException ist Elternteil DataAccessException beide Methoden ausgeführt werden ...

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

    } 
} 

und in Protokoll gibt es:

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

Maven Abhängigkeiten:

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

Wenn zwei Ratschläge im gleichen Aspekt definiert beide müssen an der gleichen Stelle kommen laufen, wird die Bestellung nicht definiert (da gibt es keine Möglichkeit, die Erklärung, um über Reflektion abzurufen für javac- kompilierte Klassen). Ziehen Sie in Erwägung, solche Beratungsmethoden in einer Beratungsmethode pro Verknüpfungspunkt in jeder Aspektklasse zusammenzufassen oder die Ratschläge in separate Aspektklassen umzuwandeln, die auf der Aspektebene geordnet werden können.

Wenn ich folgende Modifikation von MyAspect versucht:

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 
    } 

} 

Protokoll geändert:

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

und wann Exception geändert Ich habe:

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 
    } 

} 

das Protokoll war

DAE 
RE - class java.lang.NullPointerException 

glaube ich, dass Lösung für Ihr "Problem" zwei Aspekte zu haben, statt ein und definieren Sie die Bestellung:

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 
    } 

} 

und

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

} 
+0

Ich denke, dass der Fluss hier anders ist als meiner. Sie loggen einfach ein und der Abfangjäger führt alle aus. Ich, in der ersten Methode, werfen eine andere Ausnahme, die nicht mit der zweiten Methode übereinstimmen. – Francesco

+0

Wie ich schrieb, teilen Sie wichtige Details nicht. Ich habe versucht, "IllegalArgumentException" zu werfen, aber es war sehr ähnlich, ich werde das als Antwort erwähnen ... – Betlista

1

Wie wäre es ein @Around mit Rat? Sie können einfach Typ-Safe try-catch verwenden, keine Notwendigkeit, instanceof oder Reflexion zu verwenden.

Hier ist ein Beispielcode, den ich mit Hilfe von AspectJ anstelle von Spring AOP kompiliert habe, weil ich kein Spring-Benutzer bin. Der Pointcut sollte trotzdem gleich sein.

Helper Klassen:

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

Treiber Anwendung (plain Java, keine Notwendigkeit Frühling verwenden):

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

Richtung:

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

Konsolenprotokoll:

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 
Verwandte Themen