3

当发生某些特定异常时,我正在尝试添加一些监视。例如,如果我有这样的方面:

@Aspect
public class LogAspect {

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

和测试类:

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

作为输出,我将得到:

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

我想知道是否有办法让我在抛出异常后拦截方法执行,在我的建议中做一些事情并继续在相应的 catch 块中执行代码?这样,如果我打电话,divideByZeroWithCatch()我可以得到:

Some logging stuff
Can not divide by zero 
4

2 回答 2

9

是的你可以。你需要一个handler()切入点:

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

日志输出,假设类Example在包中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)

更新:如果您想知道异常处理程序的位置,有一个简单的方法:使用封闭连接点的静态部分。您还可以获得有关参数名称和类型等的信息。只需使用代码完成来查看哪些方法可用。

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

现在像这样更新您的代码:

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

然后控制台日志说:

***** 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)

如果这对你来说足够好,那么你很好。但请注意,静态部分不是完整的连接点,因此您无法从那里访问参数值。为此,您必须进行手动簿记。这可能很昂贵,并且会减慢您的应用程序。但是对于它的价值,我将向您展示如何做到这一点:

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

为什么我们需要一个ThreadLocal成员来进行连接点簿记?好吧,因为显然我们会在多线程应用程序中遇到问题。

现在控制台日志说:

***** 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!
    de.scrum_master.app.Example@4783da3f
    @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)
于 2017-02-07T15:12:39.943 回答
0

根据 AspectJ 功能的实际实现,这是可能的。请参阅kriegaex 的答案以了解如何操作

但是,在某些场景中使用 AspectJ 功能时,您可能会发现并非所有切入点定义都受支持。一个例子是 Spring Framework AOP,它只支持 AspectJ 特性的一个子集。然后你可以做的是有两种方法:一种不公开但可检测的方法,它不捕获异常(你将检测的那个),以及捕获的暴露方法。像这样:

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

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

之后,您可以在 上切入点divideByZeroNoCatch,这将使您能够进行 AfterThrowing。显然,切入点必须稍作改变。如果您的 AspectJ 实现不支持检测非公共方法,这将不起作用。

于 2017-02-07T14:55:22.800 回答