如何使用AspectJ

时间:2017-02-07 14:35:07

标签: java exception-handling aop aspectj aspects

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

@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 

2 个答案:

答案 0 :(得分:8)

是的,你可以。你需要一个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)

答案 1 :(得分:0)

根据AspectJ功能的实际实现,它是可能的。请参阅kriegaex's answer for how to do it

但是,在某些情况下使用AspectJ功能时,您可能会发现并非所有切入点定义都受支持。其中一个例子是Spring Framework AOP,only supports a subset of AspectJ features。 你可以做的是有两种方法:捕获异常(你将要检测的异常)的非暴露但可检测的方法,以及捕获的暴露方法。 像这样:

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的实现不支持检测非公共方法,那么这将不起作用。