当前位置: 首页 > 面试题库 >

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

艾嘉石
2023-03-14
问题内容

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

@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

问题答案:

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


 类似资料:
  • 我正在尝试在发生特定异常时添加一些监视。例如,如果我有这样一个方面: 和测试等级: 作为输出,我将得到: 我想知道是否有办法在抛出异常之后拦截方法执行,在我的建议中做些什么,然后继续在相应的catch块中执行代码?因此,如果我调用我可以得到:

  • 我正在使用Spring并试图用AspectJ编写示例应用程序。我需要学习如何拦截静态方法调用。在我的示例中,我尝试截取main方法,如下所示: Spring配置文件: 主要方法: 协会本身: 但是当我运行应用程序时,唯一的字符串正在打印。

  • 但是,我想改变它,这样从域层抛出的异常将在AOP层中处理。AOP层会产生某种错误响应,并将其发送回spring控制器/web服务层。 我可以创建一个IBizResponse并创建它的两个子类/接口,可能是SuccessResponse和ErrorResponse,并使域层方法返回IBizResponse。但是,我不知道如何使AOP将ErrorResponse对象返回到服务层。

  • 我试图执行一些适用于代码中所有的通用逻辑。我知道我可以编写一个来拦截快乐路径。但是,我想连接到异常处理生命周期中,以便在呈现错误响应之前执行一些常见的逻辑,比如日志记录。 在Spring Boot/Spring MVC中有这样做的方法吗?如果可能的话,我希望避免为此编写servlet过滤器。

  • 我的情况如下:我有一个loggingapect,其中有几个切入点与我的主应用程序中的特定方法执行相匹配。相应的建议机构基本上看起来都很相似,导致大量代码重复: 不过也有一些变化。有时是切入点 经常发生的事情是我手动告诉记录器 在打印第一条消息后,即在调用procedue()之前,增加缩进级别,并且 在打印最终消息之前(如果打印了任何消息),即直接在返回后降低缩进级别 我的想法是,我想用一个切入点编