使用 AspectJ 处理 AOP 中 proceed() 赋值中的无效方法

Handling void methods in assignment from proceed() in AOP using AspectJ

我正在编写一个非常通用的代码来捕获 return 类型,使用 around 作为 result = proceed();,然后是 return result;

一些方法属于 void 类型。例如

void doPrint() { 
   System.out.println("Doing something"); 
}

这些 return 类型 void 的方法如何与 return 赋值或抛出异常的方法一起以通用方式处理?

我目前的代码是:

import java.util.Arrays;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.aspectj.lang.SoftException;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.SourceLocation;

public aspect LogInjector {
    private pointcut executionJoinPoints(): !within(LogInjector) && execution (* *.*(..));

    Object around(): executionJoinPoints(){
        SourceLocation loc;
        CodeSignature sig;
        
        Class<?> type;
        
        Logger logger;
        
        Object result;
        try {
            loc = thisJoinPointStaticPart.getSourceLocation();
            sig = (CodeSignature) thisJoinPointStaticPart.getSignature();
            
            type = loc.getWithinType();
            if (type == null) type = sig.getDeclaringType();
            
            logger = LogManager.getLogger(type);
            
            result = proceed();
            return result;
        } catch (RuntimeException rte) {
            result = rte;
            throw rte;
        } catch (Throwable t) {
            result = t;
            throw new SoftException(t);
        } finally {
            logger.trace("Source location: {} | Join Point: {} | Signature: {} | Args: {} | Result: {}", loc, thisJoinPointStaticPart, sig, Arrays.deepToString(thisJoinPoint.getArgs()), result);
        }
    }

}

根据 this answer by this user 改编的更正。

在这个问题的第一个版本中,我评论说你在 result = proceed(); 之后忘记了 return result;,后来你已经更正了。这是很好的第一步。但是您的代码仍然无法编译,因为:

  • around() 建议未声明 [​​=119=] 类型。
  • 您不能从 around() 建议中抛出 Throwable,只能抛出 RuntimeException。因此,您需要将每个选中的 ExceptionErrorThrowable 包装在类似 AspectJ 的 SoftException 中或简单地包装在 RuntimeException.

所以请在 post 编写 non-compilable 代码时,请提及这一事实以及 post 您遇到的编译器错误。不要假装代码有效,只是关于 void 方法处理的细节问题。

进一步的问题是:

  • 您打印的签名是多余的,因为您还打印了已经包含相同签名的连接点。
  • 您确定了声明类型但从未打印它。
  • 您导入 org.aspectj.ajdt.internal.compiler.ast.Proceed,使方面依赖于 non-AspectJ class。更准确地说,class if 来自 AJDT(AspectJ 开发工具),AspectJ 的 Eclipse 插件。

我也不太确定用捕获的异常覆盖结果并打印它是个好主意,但在我的下面MCVE(这是你的工作,你又一次没有提供'就像上一个问题一样做)我保持不变。

驱动申请:

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    Application application = new Application();
    application.doSomething("foo");
    try {
      application.doSomethingSpecial("bar");
    } catch (Exception e) {
      e.printStackTrace();
    }
    application.doSomethingElse("zot");
  }

  public int doSomething(String string) {
    System.out.println("Doing something: " + string);
    return 42;
  }

  public void doSomethingSpecial(String string) throws Exception {
    throw new Exception("checked exception");
  }

  public void doSomethingElse(String string) {
    throw new IllegalArgumentException("runtime exception");
  }
}

看点:

请注意,我删除了 Log4J 并只打印到控制台,以便将代码归结为重要部分。您可以轻松地再次添加库。我不想手动将它添加到我的示例程序中,然后尝试它是 Log4J 1.x 还是 2.x.

package de.scrum_master.aspect;

import static java.util.Arrays.deepToString;

import org.aspectj.lang.SoftException;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.SourceLocation;

public aspect LogInjector {
  private pointcut executionJoinPoints() :
    !within(LogInjector) && execution (* *(..));

  Object around() : executionJoinPoints() {
    SourceLocation sourceLocation = thisJoinPointStaticPart.getSourceLocation();
    CodeSignature signature = (CodeSignature) thisJoinPointStaticPart.getSignature();
    Class<?> type = sourceLocation.getWithinType();
    if (type == null)
      type = signature.getDeclaringType();
    Object result = null;
    try {
      result = proceed();
      return result;
    } catch (RuntimeException rte) {
      result = rte;
      throw rte;
    } catch (Throwable t) {
      result = t;
      throw new SoftException(t);
    } finally {
      System.out.printf(
        "Source location: %s | Type: %s | Join Point: %s | Args: %s | Result: %s%n",
        sourceLocation, type, thisJoinPoint, deepToString(thisJoinPoint.getArgs()), result
      );
    }
  }

}

如您所见,方面只是 re-throws 运行时异常(无需将它们包装到另一个运行时异常中)并包装其他可抛出的对象。这也是必要的,因为否则通过调用层次结构抛出的异常将像俄罗斯套娃一样被多次包装,这是我们想要避免的。

控制台日志:

Doing something: foo
Source location: Application.java:15 | Type: class de.scrum_master.app.Application | Join Point: execution(int de.scrum_master.app.Application.doSomething(String)) | Args: [foo] | Result: 42
Source location: Application.java:20 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.doSomethingSpecial(String)) | Args: [bar] | Result: java.lang.Exception: checked exception
org.aspectj.lang.SoftException
    at de.scrum_master.app.Application.doSomethingSpecial_aroundBody5$advice(Application.java:28)
    at de.scrum_master.app.Application.doSomethingSpecial(Application.java:1)
    at de.scrum_master.app.Application.main_aroundBody0(Application.java:8)
    at de.scrum_master.app.Application.main_aroundBody1$advice(Application.java:21)
    at de.scrum_master.app.Application.main(Application.java:1)
Caused by: java.lang.Exception: checked exception
    at de.scrum_master.app.Application.doSomethingSpecial_aroundBody4(Application.java:21)
    at de.scrum_master.app.Application.doSomethingSpecial_aroundBody5$advice(Application.java:21)
    ... 4 more
Source location: Application.java:24 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.doSomethingElse(String)) | Args: [zot] | Result: java.lang.IllegalArgumentException: runtime exception
Source location: Application.java:4 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.main(String[])) | Args: [[--command, --option=123]] | Result: java.lang.IllegalArgumentException: runtime exception
Exception in thread "main" java.lang.IllegalArgumentException: runtime exception
    at de.scrum_master.app.Application.doSomethingElse_aroundBody6(Application.java:25)
    at de.scrum_master.app.Application.doSomethingElse_aroundBody7$advice(Application.java:21)
    at de.scrum_master.app.Application.doSomethingElse(Application.java:1)
    at de.scrum_master.app.Application.main_aroundBody0(Application.java:12)
    at de.scrum_master.app.Application.main_aroundBody1$advice(Application.java:21)
    at de.scrum_master.app.Application.main(Application.java:1)

日志显示 void 和 non-void 方法处理得很好,甚至 main() 参数数组的内容 - 我使用命令行参数启动示例应用程序 --command --option=123 - 打印得很好,因为我使用 Arrays.deepToString(thisJoinPoint.getArgs()) 而不是 Arrays.toString(thisJoinPoint.getArgs())


所以,就像我说的:我不明白为什么您认为 void 方法有问题,而实际上您还有一大堆其他问题。您是否曾经阅读过 AspectJ 手册或教程并使用示例代码开始,或者您只是在使用“试错”方法?

P.S.: 我永远不会在方面中使用源代码位置,因为方面不是调试器,源代码无论如何都容易重构。如果代码是在没有调试信息的情况下编译的,那么字节码中无论如何都没有源位置信息,因此您不能依赖它。一般而言,方面或日志记录不能替代调试器。


更新: 与您的 相比,您现在不会在目标方法调用之前和之后都记录,而只会在之后记录。在这种情况下,after() returningafter() throwing 建议的组合将更有效且更易于实现,因为您可以避免异常处理、re-throwing 和 finally 块。在另一个问题中,我推荐了一个 around() 建议我这样做是因为我看到你原来的 before()after() 建议每个都必须确定相同的信息才能记录它,即两次每个方法。但是如果你只需要一次,around()其实是不需要的。


更新 2:您问:

I am running into another problem. By throwing SoftException and RuntimeException the catch blocks are not catching the exceptions supposed to be thrown and caught as per the method signatures in libraries as per normal behaviour.

如果你想要异常不变,使用上面提到的after() returning(打印结果)和after() throwing(未改变的异常,不需要包装它们)的组合。有关详细信息,请参阅 AspectJ manual

package de.scrum_master.aspect;

import static java.util.Arrays.deepToString;

import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.SourceLocation;

public aspect LogInjector {
  private pointcut executionJoinPoints() :
    !within(LogInjector) && execution (* *(..));

  after() returning (Object result): executionJoinPoints() {
    SourceLocation sourceLocation = thisJoinPointStaticPart.getSourceLocation();
    CodeSignature signature = (CodeSignature) thisJoinPointStaticPart.getSignature();
    Class<?> type = sourceLocation.getWithinType();
    if (type == null)
      type = signature.getDeclaringType();
    System.out.printf("Source location: %s | Type: %s | Join Point: %s | Args: %s | Result: %s%n",
      sourceLocation, type, thisJoinPoint, deepToString(thisJoinPoint.getArgs()), result
    );
  }

  after() throwing (Throwable error): executionJoinPoints() {
    SourceLocation sourceLocation = thisJoinPointStaticPart.getSourceLocation();
    CodeSignature signature = (CodeSignature) thisJoinPointStaticPart.getSignature();
    Class<?> type = sourceLocation.getWithinType();
    if (type == null)
      type = signature.getDeclaringType();
    System.out.printf("Source location: %s | Type: %s | Join Point: %s | Args: %s | Error: %s%n",
      sourceLocation, type, thisJoinPoint, deepToString(thisJoinPoint.getArgs()), error
    );
  }

}

控制台日志将更改为:

Doing something: foo
Source location: Application.java:15 | Type: class de.scrum_master.app.Application | Join Point: execution(int de.scrum_master.app.Application.doSomething(String)) | Args: [foo] | Result: 42
Source location: Application.java:20 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.doSomethingSpecial(String)) | Args: [bar] | Error: java.lang.Exception: checked exception
java.lang.Exception: checked exception
    at de.scrum_master.app.Application.doSomethingSpecial(Application.java:21)
    at de.scrum_master.app.Application.main(Application.java:8)
Source location: Application.java:24 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.doSomethingElse(String)) | Args: [zot] | Error: java.lang.IllegalArgumentException: runtime exception
Source location: Application.java:4 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.main(String[])) | Args: [[--command, --option=123]] | Error: java.lang.IllegalArgumentException: runtime exception
Exception in thread "main" java.lang.IllegalArgumentException: runtime exception
    at de.scrum_master.app.Application.doSomethingElse(Application.java:25)
    at de.scrum_master.app.Application.main(Application.java:12)