如何使用AspectJ拦截处理自身exception的方法

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

@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 

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

 Some logging stuff Can not divide by zero 

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

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

 @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 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成员来进行连接点记录? 好吧,因为很明显我们会在multithreading应用程序中遇到问题。

现在控制台日志说:

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

根据AspectJfunction的实际实现,它是可能的。 请参阅kriegaex的答案,了解如何做到这一点 。

但是,在某些情况下使用AspectJfunction时,您可能会发现并非所有切入点定义都受支持。 其中一个例子是Spring Framework AOP, 它只支持AspectJfunction的一个子集 。 你可以做的是有两种方法:一种不暴露但可检测的方法, 它不会捕获exception(你要检测的那种),以及暴露的捕获方法。 喜欢这个:

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