Java Method Logging with AOP and Annotations

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

有时候,我想要通过 slf4jlog4j 记录每个方法的执行情况,查看它接收的参数、返回值以及每次执行所花费的时间。我是通过 AspectJjcabi-aspects 和 Java 6 注解来实现的。

这是我在log4j输出中看到的内容:

不错,是吗?现在,让我们来看看它是如何工作的。

注解是在Java 6中引入的一种技术。它是一种元编程工具,不改变代码的工作方式,而是给某些元素(方法、类或变量)打上标记。换句话说,注解只是附加在代码上可见且可读的标记。有些注解仅在编译时可见—它们在编译后的.class文件中不存在。其他注解在编译后仍然可见,并且可以在运行时访问。

例如,@Override是第一种类型的注解(其保留类型为SOURCE),而来自JUnit的@Test是第二种类型的注解(保留类型为RUNTIME)。@Loggable—我在上面的脚本中使用的注解—是第二种类型的注解,来自jcabi-aspects。它在编译后的.class文件中与字节码一起保留。

再次强调,重要的是要理解,即使方法power()已经被注解并编译,它目前还没有向slf4j发送任何内容。它只是包含一个标记,表示“请记录我的执行”。

AOP 是一种有用的技术,它使得可以在源代码中添加可执行块而不需要显式地更改它。在我们的例子中,我们不想在类内部记录方法的执行。相反,我们希望另一个类拦截每次调用方法 power(),测量其执行时间,并将此信息发送到 slf4j。

我们希望拦截器能够理解我们的 @Loggable 注解,并记录对特定方法 power() 的每次调用。当然,将来我们会在其他方法中放置相同的注解时,应该使用同样的拦截器。

这种情况完全符合 AOP 的最初意图—避免在多个类中重新实现某些常见的行为。

日志记录是我们主要功能的一个附加功能,我们不想用多个日志记录指令来污染我们的代码。相反,我们希望日志记录在幕后进行。

按照 AOP 的术语,我们的解决方案可以解释为创建一个切面,它在特定的连接点横切代码,并应用一个环绕通知来实现所需的功能。

让我们看一下这些神奇的词语是什么意思。但是,首先,让我们看一下jcabi-aspects如何使用AspectJ来实现它们(这只是一个简化的例子,完整的代码可以在MethodLogger.java中找到)。

这是一个带有一个名为around()环绕通知(around advice)的切面(aspect)。该切面被注解为@Aspect,而通知被注解为@Around。如上所述,这些注解只是.class文件中的标记,除了向对运行时感兴趣的人提供一些元信息外,它们不起任何作用。

注解@Around有一个参数,在这种情况下,它表示通知应该应用于一个方法,如果:

  1. 它的名字是 *(任何名字);

  2. 它的参数是 ..(任意参数);并且

  3. 它使用@Loggable进行了注释。

当要拦截注释方法的调用时,方法 around() 在执行实际方法之前执行。当要拦截对方法 power() 的调用时,方法 around() 接收一个 ProceedingJoinPoint 类的实例,并且必须返回一个对象,该对象将作为方法 power() 的结果使用。

为了调用原始方法 power(),建议必须调用 连接点 对象的 proceed() 方法。

我们编译这个切面,并将其与我们的主文件 Foo.class 一起提供在类路径中。到目前为止都很好,但是为了使我们的切面生效,我们还需要迈出最后一步—我们应该 应用 我们的建议。

Aspect weaving(切面编织)是应用建议过程的名字。切面编织器通过注入切面调用来修改原始代码。AspectJ正是这样做的。我们给它两个二进制的Java类Foo.classMethodLogger.class;它返回三个文件—修改后的Foo.classFoo$AjcClosure1.class和未修改的MethodLogger.class

为了了解哪些建议应该应用于哪些方法,AspectJ编织器使用.class文件中的注解。同时,它使用reflection来浏览类路径上的所有类。它分析了哪些方法满足@Around注解的条件。当然,它找到了我们的power()方法。

所以,有两个步骤。首先,我们使用javac编译我们的.java文件并得到两个文件。然后,AspectJ编织/修改它们并创建了自己的额外类。我们的Foo类在编织后看起来像这样:

AspectJ编织器将我们的原始功能移动到一个新的方法power_aroundBody(),并将所有power()调用重定向到切面类MethodLogger

现在,类Foo中不再只有一个power()方法,而是有四个类一起工作。从现在开始,每次调用power()时,以下是在幕后发生的事情:

hide footbox skinparam sequence { ArrowFontName “Courier New” ArrowFontSize 17 ParticipantFontName “Courier New” ParticipantFontSize 17 ActorFontSize 17 }

actor “Client” as client participant “Foo” as foo participant “MethodLogger” as aspect participant “JoinPoint” as point participant “Foo$AjcClosure1” as closure

client -> foo : power() activate foo foo -> aspect : around() activate aspect aspect -> point : proceed() activate point point -> closure : run() activate closure closure -> foo : power_aroundBody() activate foo #228b22 foo –> closure deactivate foo closure –> point deactivate closure point –> aspect deactivate point aspect –> foo deactivate aspect foo –> client deactivate foo

在图表上,小绿色生命线表示power()方法的原始功能。

如您所见,切面编织过程通过连接类和切面,在它们之间通过连接点传递调用。没有编织,类和切面只是带有附加注解的编译Java二进制文件。

jcabi-aspects是一个JAR库,其中包含Loggable注解和MethodLogger切面(顺便说一下,还有很多切面和注解)。您不需要为方法日志编写自己的切面。只需将一些依赖项添加到类路径中,并配置jcabi-maven-plugin以进行切面编织(在Maven Central获取它们的最新版本)。

由于这个编织过程需要大量的配置工作,我创建了一个方便的Maven插件,其中包含一个ajc目标,可以完成整个方面编织工作。你可以直接使用AspectJ,但我建议你使用jcabi-maven-plugin

就是这样。现在你可以使用@com.jcabi.aspects.Loggable注解,你的方法将通过slf4j进行日志记录。

如果有任何问题无法按照说明工作,请随时提交GitHub问题

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-27 at 10:25

sixnines availability badge   GitHub stars