Introduction to the jcabi-aspects AOP Annotations Library – jcabi-aspects AOP注解库简介

最后修改: 2020年 2月 21日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this quick tutorial, we’ll explore the jcabi-aspects Java library, a collection of handy annotations that modify the behavior of Java application using aspect-oriented programming (AOP).

在这个快速教程中,我们将探讨jcabi-aspects Java库,这是一个方便的注释集合,可以使用面向方面编程(AOP)来修改Java应用程序的行为。

The jcabi-aspects library provides annotations like @Async, @Loggable, and @RetryOnFailure, that are useful in performing certain operations efficiently using AOP. At the same time, they help to reduce the amount of boilerplate code in our application. The library requires AspectJ to weave the aspects into compiled classes.

jcabi-aspects库提供了诸如@Async@Loggable@RetryOnFailure等注解,这些注解对于使用AOP有效地执行某些操作非常有用。同时,它们有助于减少我们应用程序中的模板代码量。该库需要AspectJ来将这些方面编织到已编译的类中。

2. Setup

2.设置

First, we’ll add the latest jcabi-aspects Maven dependency to the pom.xml:

首先,我们要把最新的jcabi-aspectsMaven依赖性添加到pom.xml

<dependency>
    <groupId>com.jcabi</groupId>
    <artifactId>jcabi-aspects</artifactId>
    <version>0.22.6</version>
</dependency>

The jcabi-aspects library requires AspectJ runtime support to act. Therefore, let’s add the aspectjrt Maven dependency:

jcabi-aspects库需要AspectJ运行时支持才能发挥作用。因此,让我们添加aspectjrt Maven依赖。

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.2</version>
    <scope>runtime</scope>
</dependency>

Next, let’s add the jcabi-maven-plugin plugin that weaves the binaries with AspectJ aspects at compile-time. The plugin provides the ajc goal that does the automatic weaving:

接下来,让我们添加jcabi-maven-plugin插件,在编译时将二进制文件与AspectJ方面编织起来。该插件提供的ajc目标可以进行自动编织。

<plugin>
    <groupId>com.jcabi</groupId>
    <artifactId>jcabi-maven-plugin</artifactId>
    <version>0.14.1</version>
    <executions>
        <execution>
            <goals>
                <goal>ajc</goal>
            </goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>
    </dependencies>
</plugin>

Last, let’s compile the classes using the Maven command:

最后,让我们用Maven命令编译这些类。

mvn clean package

The logs generated by the jcabi-maven-plugin at compilation will look like:

编译时由jcabi-maven-plugin产生的日志将看起来像。

[INFO] --- jcabi-maven-plugin:0.14.1:ajc (default) @ jcabi ---
[INFO] jcabi-aspects 0.18/55a5c13 started new daemon thread jcabi-loggable for watching of 
  @Loggable annotated methods
[INFO] Unwoven classes will be copied to /jcabi/target/unwoven
[INFO] Created temp dir /jcabi/target/jcabi-ajc
[INFO] jcabi-aspects 0.18/55a5c13 started new daemon thread jcabi-cacheable for automated
  cleaning of expired @Cacheable values
[INFO] ajc result: 11 file(s) processed, 0 pointcut(s) woven, 0 error(s), 0 warning(s)

Now that we know how to add the library to our project, let’s see some if its annotations in action.

现在我们知道了如何将该库添加到我们的项目中,让我们看看它的一些注解的作用。

3. @Async

3. @Async

The @Async annotation allows executing the method asynchronously. However, it is only compatible with methods that return a void or Future type.

@Async注解允许异步地执行方法。然而,它只与返回voidFuture类型的方法兼容。

Let’s write a displayFactorial method that displays the factorial of a number asynchronously:

让我们写一个displayFactorial方法,以异步方式显示一个数字的阶乘。

@Async
public static void displayFactorial(int number) {
    long result = factorial(number);
    System.out.println(result);
}

Then, we’ll recompile the class to let Maven weave the aspect for the @Async annotation. Last, we can run our example:

然后,我们重新编译该类,让Maven为@Async注解编织一个方面。最后,我们可以运行我们的例子。

[main] INFO com.jcabi.aspects.aj.NamedThreads - 
jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-async for Asynchronous method execution

As we can see from the log, the library creates a separate daemon thread jcabi-async to perform all asynchronous operations.

我们可以从日志中看到,该库创建了一个单独的守护线程jcabi-async 来执行所有异步操作

Now, let’s use the @Async annotation to return a Future instance:

现在,让我们使用@Async注解来返回一个Future实例。

@Async
public static Future<Long> getFactorial(int number) {
    Future<Long> factorialFuture = CompletableFuture.supplyAsync(() -> factorial(number));
    return factorialFuture;
}

If we use @Async on a method that does not return void or Future, an exception will be thrown at runtime when we invoke it.

如果我们在一个没有返回voidFuture的方法上使用@Async,当我们调用它时,将在运行时抛出一个异常。

4. @Cacheable

4.@Cacheable

The @Cacheable annotation allows caching a method’s results to avoid duplicate calculations.

@Cacheable注解允许缓存一个方法的结果,以避免重复计算。

For instance, let’s write a cacheExchangeRates method that returns the latest exchange rates:

例如,让我们写一个cacheExchangeRates方法,返回最新的汇率。

@Cacheable(lifetime = 2, unit = TimeUnit.SECONDS)
public static String cacheExchangeRates() {
    String result = null;
    try {
        URL exchangeRateUrl = new URL("https://api.exchangeratesapi.io/latest");
        URLConnection con = exchangeRateUrl.openConnection();
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        result = in.readLine();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;
}

Here, the cached result will have a lifetime of 2 seconds. Similarly, we can make a result cacheable forever by using:

这里,缓存的结果将有2秒的寿命。同样地,我们可以通过使用以下方法使一个结果永远可以被缓存。

@Cacheable(forever = true)

Once we recompile the class and execute it again, the library will log the details of two daemon threads that handle the caching mechanism:

一旦我们重新编译该类并再次执行,该库将记录处理缓存机制的两个守护线程的细节。

[main] INFO com.jcabi.aspects.aj.NamedThreads - 
jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-cacheable-clean for automated 
  cleaning of expired @Cacheable values
[main] INFO com.jcabi.aspects.aj.NamedThreads - 
jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-cacheable-update for async 
  update of expired @Cacheable values

When we invoke our cacheExchangeRates method, the library will cache the result and log the details of the execution:

当我们调用我们的cacheExchangeRates方法时,该库将缓存结果并记录执行的细节。

[main] INFO com.baeldung.jcabi.JcabiAspectJ - #cacheExchangeRates(): 
'{"rates":{"CAD":1.458,"HKD":8.5039,"ISK":137.9,"P..364..:4.5425},"base":"EUR","date":"2020-02-10"}'
  cached in 560ms, valid for 2s

So, if invoked again (within 2 seconds), cacheExchangeRates will return the result from the cache:

因此,如果再次调用(2秒内),cacheExchangeRates将返回缓存的结果。

[main] INFO com.baeldung.jcabi.JcabiAspectJ - #cacheExchangeRates(): 
'{"rates":{"CAD":1.458,"HKD":8.5039,"ISK":137.9,"P..364..:4.5425},"base":"EUR","date":"2020-02-10"}'
  from cache (hit #1, 563ms old)

If the method throws an exception, the result won’t be cached.

如果该方法抛出一个异常,那么结果将不会被缓存。

5. @Loggable

5.@Loggable

The library provides the @Loggable annotation for simple logging using the SLF4J logging facility.

该库提供了@Loggable注解,用于使用SLF4J日志设施进行简单的日志记录。

Let’s add the @Loggable annotation to our displayFactorial and cacheExchangeRates methods:

让我们将@Loggable注解添加到我们的displayFactorialcacheExchangeRates方法。

@Loggable
@Async
public static void displayFactorial(int number) {
    ...
}

@Loggable
@Cacheable(lifetime = 2, unit = TimeUnit.SECONDS)
public static String cacheExchangeRates() {
    ...
}

Then, after recompilation, the annotation will log the method name, return value, and execution time:

然后,在重新编译后,注释将记录方法名称、返回值和执行时间。

[main] INFO com.baeldung.jcabi.JcabiAspectJ - #displayFactorial(): in 1.16ms
[main] INFO com.baeldung.jcabi.JcabiAspectJ - #cacheExchangeRates(): 
'{"rates":{"CAD":1.458,"HKD":8.5039,"ISK":137.9,"P..364..:4.5425},"base":"EUR","date":"2020-02-10"}'
  in 556.92ms

6. @LogExceptions

6.@LogExceptions

Similar to @Loggable, we can use the @LogExceptions annotation to log only the exceptions thrown by a method.

@Loggable类似,我们可以使用@LogExceptions注解来只记录一个方法抛出的异常。

Let’s use @LogExceptions on a method divideByZero that will throw an ArithmeticException:

让我们在一个将抛出ArithmeticException的方法divideByZero上使用@LogExceptions

@LogExceptions
public static void divideByZero() {
    int x = 1/0;
}

The execution of the method will log the exception and also throw the exception:

该方法的执行将记录该异常,同时抛出该异常。

[main] WARN com.baeldung.jcabi.JcabiAspectJ - java.lang.ArithmeticException: / by zero
    at com.baeldung.jcabi.JcabiAspectJ.divideByZero_aroundBody12(JcabiAspectJ.java:77)

java.lang.ArithmeticException: / by zero
    at com.baeldung.jcabi.JcabiAspectJ.divideByZero_aroundBody12(JcabiAspectJ.java:77)
    ...

7. @Quietly

7.@Quietly

The @Quietly annotation is similar to @LogExceptions, except that it doesn’t propagate any exception thrown by the method. Instead, it just logs them.

@Quietly注解与@LogExceptions类似,只是它不传播方法抛出的任何异常。相反,它只是记录它们。

Let’s add the @Quietly annotation to our divideByZero method:

让我们将@Quietly注解添加到我们的divideByZero方法。

@Quietly
public static void divideByZero() {
    int x = 1/0;
}

Hence, the annotation will swallow the exception and only log the details of the exception that would’ve otherwise been thrown:

因此,注解会吞掉这个异常,只记录本来会被抛出的异常的细节。

[main] WARN com.baeldung.jcabi.JcabiAspectJ - java.lang.ArithmeticException: / by zero
    at com.baeldung.jcabi.JcabiAspectJ.divideByZero_aroundBody12(JcabiAspectJ.java:77)

The @Quietly annotation is only compatible with methods that have a void return type.

@Quietly注解只与具有void返回类型的方法兼容。

8. @RetryOnFailure

8.@RetryOnFailure

The @RetryOnFailure annotation allows us to repeat the execution of a method in the event of an exception or failure.

@RetryOnFailure注解允许我们在发生异常或失败时重复执行一个方法。

For example, let’s add the @RetryOnFailure annotation to our divideByZero method:

例如,让我们将@RetryOnFailure注解添加到我们的divideByZero方法。

@RetryOnFailure(attempts = 2)
@Quietly
public static void divideByZero() {
    int x = 1/0;
}

So, if the method throws an exception, the AOP advice will attempt to execute it twice:

因此,如果该方法抛出一个异常,AOP建议将尝试执行两次。

[main] WARN com.baeldung.jcabi.JcabiAspectJ - 
#divideByZero(): attempt #1 of 2 failed in 147µs with java.lang.ArithmeticException: / by zero
[main] WARN com.baeldung.jcabi.JcabiAspectJ - 
#divideByZero(): attempt #2 of 2 failed in 110µs with java.lang.ArithmeticException: / by zero

Also, we can define other parameters like delay, unit, and types, while declaring the @RetryOnFailure annotation:

另外,我们可以在声明@RetryOnFailure注解的同时定义其他参数,如delayunittypes

@RetryOnFailure(attempts = 3, delay = 5, unit = TimeUnit.SECONDS, 
  types = {java.lang.NumberFormatException.class})

In this case, the AOP advice will attempt the method thrice, with a delay of 5 seconds between attempts, only if the method throws a NumberFormatException.

在这种情况下,AOP建议将尝试该方法三次,两次尝试之间有5秒的延迟,只有在该方法抛出NumberFormatException的情况下。

9. @UnitedThrow

9.@UnitedThrow

The @UnitedThrow annotation allows us to catch all exceptions thrown by a method and wrap it in an exception we specify. Thus, it unifies the exceptions thrown by the method.

@UnitedThrow注解允许我们捕获一个方法抛出的所有异常,并将其包装在我们指定的异常中。因此,它统一了该方法抛出的异常。

For instance, let’s create a method processFile that throws IOException and InterruptedException:

例如,让我们创建一个方法processFile,抛出IOExceptionInterruptedException

@UnitedThrow(IllegalStateException.class)
public static void processFile() throws IOException, InterruptedException {
    BufferedReader reader = new BufferedReader(new FileReader("baeldung.txt"));
    reader.readLine();
    // additional file processing
}

Here, we’ve added the annotation to wrap all exceptions into IllegalStateException. Therefore, when the method is invoked, the stack trace of the exception will look like:

在这里,我们添加了注解,将所有的异常包裹在IllegalStateException中。因此,当该方法被调用时,异常的堆栈跟踪将看起来像。

java.lang.IllegalStateException: java.io.FileNotFoundException: baeldung.txt (No such file or directory)
    at com.baeldung.jcabi.JcabiAspectJ.processFile(JcabiAspectJ.java:92)
    at com.baeldung.jcabi.JcabiAspectJ.main(JcabiAspectJ.java:39)
Caused by: java.io.FileNotFoundException: baeldung.txt (No such file or directory)
    at java.io.FileInputStream.open0(Native Method)
    ...

10. Conclusion

10.结语

In this article, we’ve explored the jcabi-aspects Java library.

在这篇文章中,我们已经探讨了jcabi-aspects Java库。

First, we’ve seen a quick way to set up the library in our Maven project using jcabi-maven-plugin.

首先,我们已经看到了一种快速的方法,即使用jcabi-maven-plugin在我们的Maven项目中设置该库。

Then, we examined a few handy annotations, like @Async, @Loggable, and @RetryOnFailure, that modify the behavior of the Java application using AOP.

然后,我们研究了一些方便的注解,如@Async@Loggable@RetryOnFailure,它们使用AOP来修改Java应用程序的行为。

As usual, all the code implementations are available over on GitHub.

像往常一样,所有的代码实现都可以在GitHub上找到