2017-02-07 1 views
0

J'essaie d'ajouter une surveillance lorsqu'une exception spécifique se produit. Par exemple, si j'ai un aspect comme celui-ci:Comment intercepter la méthode qui gère ses propres exceptions à l'aide d'AspectJ

@Aspect 
public class LogAspect { 

    @AfterThrowing(value = "execution(* *(..))", throwing = "e") 
    public void log(JoinPoint joinPoint, Throwable e){ 
    System.out.println("Some logging stuff"); 
    } 
} 

Et classe test:

public class Example { 


    public void divideByZeroWithCatch(){ 
    try{ 
     int a = 5/0; 
    } 
    catch (ArithmeticException e){ 
     System.out.println("Can not divide by zero"); 
    } 
    } 

    public void divideByZeroWithNoCatch(){ 
    int b = 5/0; 
    } 

    public static void main (String [] args){ 
    Example e = new Example(); 
    System.out.println("***** Calling method with catch block *****"); 
    e.divideByZeroWithCatch(); 
    System.out.println("***** Calling method without catch block *****"); 
    e.divideByZeroWithNoCatch(); 
    } 
} 

En sortie j'obtiendrai:

***** Calling method with catch block ***** 
Can not divide by zero 
***** Calling method without catch block ***** 
Some logging stuff 

Je me demandais s'il y a moyen pour moi d'intercepter l'exécution de la méthode juste après avoir levé l'exception, faire quelque chose dans mon conseil et continuer à exécuter le code dans le bloc catch correspondant? Alors que si j'appelle divideByZeroWithCatch() je peux obtenir:

Some logging stuff 
Can not divide by zero 

Répondre

4

Oui, vous pouvez. Vous avez besoin d'un handler() pointcut:

package de.scrum_master.aspect; 

import org.aspectj.lang.JoinPoint; 
import org.aspectj.lang.annotation.AfterThrowing; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.annotation.Before; 

@Aspect 
public class LogAspect { 
    @AfterThrowing(value = "execution(* *(..))", throwing = "e") 
    public void log(JoinPoint thisJoinPoint, Throwable e) { 
     System.out.println(thisJoinPoint + " -> " + e); 
    } 

    @Before("handler(*) && args(e)") 
    public void logCaughtException(JoinPoint thisJoinPoint, Exception e) { 
     System.out.println(thisJoinPoint + " -> " + e); 
    } 
} 

sortie du journal, en supposant la classe Example est dans le paquet de.scrum_master.app:

***** Calling method with catch block ***** 
handler(catch(ArithmeticException)) -> java.lang.ArithmeticException:/by zero 
Can not divide by zero 
***** Calling method without catch block ***** 
execution(void de.scrum_master.app.Example.divideByZeroWithNoCatch()) -> java.lang.ArithmeticException:/by zero 
execution(void de.scrum_master.app.Example.main(String[])) -> java.lang.ArithmeticException:/by zero 
Exception in thread "main" java.lang.ArithmeticException:/by zero 
    at de.scrum_master.app.Example.divideByZeroWithNoCatch(Example.java:13) 
    at de.scrum_master.app.Example.main(Example.java:21) 

Mise à jour: Si vous voulez savoir où se trouve le gestionnaire d'exception, il y a un moyen simple: utiliser la partie statique du joinpoint. Vous pouvez également obtenir des informations sur les noms et les types de paramètres, etc. Utilisez simplement l'achèvement du code pour voir quelles méthodes sont disponibles.

@Before("handler(*) && args(e)") 
public void logCaughtException(
    JoinPoint thisJoinPoint, 
    JoinPoint.EnclosingStaticPart thisEnclosingJoinPointStaticPart, 
    Exception e 
) { 
    // Exception handler 
    System.out.println(thisJoinPoint.getSignature() + " -> " + e); 

    // Method signature + parameter types/names 
    MethodSignature methodSignature = (MethodSignature) thisEnclosingJoinPointStaticPart.getSignature(); 
    System.out.println(" " + methodSignature); 
    Class<?>[] paramTypes = methodSignature.getParameterTypes(); 
    String[] paramNames = methodSignature.getParameterNames(); 
    for (int i = 0; i < paramNames.length; i++) 
     System.out.println("  " + paramTypes[i].getName() + " " + paramNames[i]); 

    // Method annotations - attention, reflection! 
    Method method = methodSignature.getMethod(); 
    for (Annotation annotation: method.getAnnotations()) 
     System.out.println(" " + annotation); 
} 

maintenant mettre à jour votre code comme ceci:

package de.scrum_master.app; 

import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 

@Retention(RetentionPolicy.RUNTIME) 
public @interface MyAnnotation { 
    int id(); 
    String name(); 
    String remark(); 
} 
package de.scrum_master.app; 

public class Example { 
    @MyAnnotation(id = 11, name = "John", remark = "my best friend") 
    public void divideByZeroWithCatch(int dividend, String someText) { 
     try { 
      int a = 5/0; 
     } catch (ArithmeticException e) { 
      System.out.println("Can not divide by zero"); 
     } 
    } 

    public void divideByZeroWithNoCatch() { 
     int b = 5/0; 
    } 

    public static void main(String[] args) { 
     Example e = new Example(); 
     System.out.println("***** Calling method with catch block *****"); 
     e.divideByZeroWithCatch(123, "Hello world!"); 
     System.out.println("***** Calling method without catch block *****"); 
     e.divideByZeroWithNoCatch(); 
    } 
} 

Ensuite, le journal de la console dit:

***** Calling method with catch block ***** 
catch(ArithmeticException) -> java.lang.ArithmeticException:/by zero 
    void de.scrum_master.app.Example.divideByZeroWithCatch(int, String) 
     int dividend 
     java.lang.String someText 
    @de.scrum_master.app.MyAnnotation(id=11, name=John, remark=my best friend) 
Can not divide by zero 
***** Calling method without catch block ***** 
execution(void de.scrum_master.app.Example.divideByZeroWithNoCatch()) -> java.lang.ArithmeticException:/by zero 
execution(void de.scrum_master.app.Example.main(String[])) -> java.lang.ArithmeticException:/by zero 
Exception in thread "main" java.lang.ArithmeticException:/by zero 
    at de.scrum_master.app.Example.divideByZeroWithNoCatch(Example.java:14) 
    at de.scrum_master.app.Example.main(Example.java:22) 

Si cela est assez bon pour vous, alors vous êtes bien. Mais attention, la partie statique n'est pas le point de connexion complet, donc vous ne pouvez pas accéder aux valeurs des paramètres à partir de là. Pour ce faire, vous devez faire une comptabilité manuelle. Et c'est peut-être cher et peut ralentir votre application.Mais pour ce qu'il vaut la peine, je vous montre comment le faire:

package de.scrum_master.aspect; 

import java.lang.annotation.Annotation; 
import java.lang.reflect.Method; 

import org.aspectj.lang.JoinPoint; 
import org.aspectj.lang.annotation.AfterThrowing; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.annotation.Before; 
import org.aspectj.lang.reflect.MethodSignature; 

@Aspect 
public class LogAspect { 
    private ThreadLocal<JoinPoint> enclosingJoinPoint; 

    @AfterThrowing(value = "execution(* *(..))", throwing = "e") 
    public void log(JoinPoint thisJoinPoint, Throwable e) { 
     System.out.println(thisJoinPoint + " -> " + e); 
    } 

    @Before("execution(* *(..)) && within(de.scrum_master.app..*)") 
    public void recordJoinPoint(JoinPoint thisJoinPoint) { 
     if (enclosingJoinPoint == null) 
      enclosingJoinPoint = ThreadLocal.withInitial(() -> thisJoinPoint); 
     else 
      enclosingJoinPoint.set(thisJoinPoint); 
    } 

    @Before("handler(*) && args(e)") 
    public void logCaughtException(JoinPoint thisJoinPoint, Exception e) { 
     // Exception handler 
     System.out.println(thisJoinPoint + " -> " + e); 

     // Method signature + parameter types/names 
     JoinPoint enclosingJP = enclosingJoinPoint.get(); 
     MethodSignature methodSignature = (MethodSignature) enclosingJP.getSignature(); 
     System.out.println(" " + methodSignature); 
     Class<?>[] paramTypes = methodSignature.getParameterTypes(); 
     String[] paramNames = methodSignature.getParameterNames(); 
     Object[] paramValues = enclosingJP.getArgs(); 
     for (int i = 0; i < paramNames.length; i++) 
      System.out.println("  " + paramTypes[i].getName() + " " + paramNames[i] + " = " + paramValues[i]); 

     // Target object upon which method is executed 
     System.out.println(" " + enclosingJP.getTarget()); 

     // Method annotations - attention, reflection! 
     Method method = methodSignature.getMethod(); 
     for (Annotation annotation: method.getAnnotations()) 
      System.out.println(" " + annotation); 
    } 
} 

Pourquoi avons-nous besoin d'un membre ThreadLocal pour la tenue de livres joinpoint? Eh bien, parce que, évidemment, nous aurions des problèmes dans les applications multithread dans le cas contraire.

Maintenant, le journal de la console dit:

***** Calling method with catch block ***** 
handler(catch(ArithmeticException)) -> java.lang.ArithmeticException:/by zero 
    void de.scrum_master.app.Example.divideByZeroWithCatch(int, String) 
     int dividend = 123 
     java.lang.String someText = Hello world! 
    [email protected] 
    @de.scrum_master.app.MyAnnotation(id=11, name=John, remark=my best friend) 
Can not divide by zero 
***** Calling method without catch block ***** 
execution(void de.scrum_master.app.Example.divideByZeroWithNoCatch()) -> java.lang.ArithmeticException:/by zero 
execution(void de.scrum_master.app.Example.main(String[])) -> java.lang.ArithmeticException:/by zero 
Exception in thread "main" java.lang.ArithmeticException:/by zero 
    at de.scrum_master.app.Example.divideByZeroWithNoCatch(Example.java:14) 
    at de.scrum_master.app.Example.main(Example.java:22) 
+0

Oh, je ne savais pas à propos de ce pointcut supplémentaire. Est-il disponible dans toutes les implémentations d'AspectJ? Pour autant que je sache, par exemple, l'implémentation de SpringAspectJ ne permet pas d'utiliser le gestionnaire, comme on peut le voir [ici (Spring-pointcut-designators)] (https://docs.spring.io/spring/docs/current/ spring-framework-reference/html/aop.html # aop-pointcuts-designators) –

+0

Je sais, mais cette question concerne AspectJ, pas Spring AOP. Ce dernier est juste "AOP Lite", et la section manuelle que vous avez liée à une mention dans la boîte grise "Autres types de point" qu'AspectJ supporte 'handler()' et Spring AOP ne le fait pas. Mais les bonnes nouvelles sont: Vous pouvez également configurer Spring pour utiliser full [AspectJ via le tissage de temps de chargement] (https://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html # aop-using-aspectj). Ensuite, vous pouvez également utiliser 'handler()'. – kriegaex

+0

oui, j'ai déjà édité ma réponse dans ce que je pense que je voulais dire, en soulignant explicitement qu'il ne s'agissait pas de "full AspectJ". Ayant seulement travaillé auparavant avec la saveur printanière, je me suis sentie là-bas, semble-t-il. –

0

Selon la mise en œuvre effective des caractéristiques AspectJ, il est possible. Voir kriegaex's answer for how to do it. Toutefois, lors de l'utilisation des fonctionnalités AspectJ dans certains scénarios, vous pouvez constater que toutes les définitions de point ne sont pas prises en charge. Un exemple de ceci est Spring Framework AOP, qui only supports a subset of AspectJ features. Ce que vous pouvez alors faire est d'avoir deux méthodes: une méthode non exposée mais instrumentable qui ne saisit pas exceptions (celui que vous allez instrument), et la méthode exposée qui attrape. Comme ceci:

protected void divideByZeroNoCatch(int arg) { 
    int r = arg/0; 
} 

public void divideByZeroSafe(int arg) { 
    try { 
    divideByZeroNoCatch(arg); 
    } catch(ArithmeticException ae) { 
    logException(ae); 
    } 
} 

Après cela, vous pouvez pointcut sur divideByZeroNoCatch, qui vous donnera la capacité de faire votre AfterThrowing. Évidemment, le pointcut devra changer un peu. Et cela ne fonctionnera pas si votre implémentation d'AspectJ ne prend pas en charge l'instrumentation de méthodes non publiques.

+0

La réponse est incorrecte. Découvrez [mine] (http://stackoverflow.com/a/42093318/1082681). – kriegaex

+0

@kriegaex, merci de le signaler. Je vais éditer ma réponse par rapport à la vôtre. –

+0

Désolé, mais votre modification est également fausse, comme je l'ai dit dans un autre commentaire sous ma réponse. Il n'y a qu'une seule implémentation d'AspectJ. Vous parlez de Spring AOP, mais Spring AOP! = AspectJ. Ce sont des choses différentes et la première ne partage que la syntaxe de base et est un petit sous-ensemble de celle-ci, implémentée d'une manière totalement différente (utilisant des proxies dynamiques), alors qu'AspectJ n'a pas besoin/utilise directement des proxies. – kriegaex