Перейти к основному содержимому

Введение в библиотеку аннотаций АОП jcabi-aspects

· 7 мин. чтения

1. Обзор

В этом кратком руководстве мы рассмотрим библиотеку Java jcabi-aspects , набор удобных аннотаций, которые изменяют поведение приложения Java с помощью аспектно-ориентированного программирования (АОП).

Библиотека jcabi-aspects предоставляет такие аннотации, как @Async , @Loggable и @RetryOnFailure , которые полезны для эффективного выполнения определенных операций с использованием АОП. В то же время они помогают уменьшить объем шаблонного кода в нашем приложении. Библиотека требует , чтобы AspectJ объединял аспекты в скомпилированные классы.

2. Настройка

Во- первых, мы добавим последнюю зависимость jcabi-aspects Maven в pom.xml :

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

Для работы библиотеки jcabi-aspects требуется поддержка времени выполнения AspectJ. Поэтому давайте добавим зависимость Aspectjrt Maven:

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

Далее добавим плагин 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>

Наконец, давайте скомпилируем классы с помощью команды Maven:

mvn clean package

Журналы, сгенерированные 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)

Теперь, когда мы знаем, как добавить библиотеку в наш проект, давайте посмотрим на ее аннотации в действии.

3. @Асинхронный

Аннотация @Async позволяет выполнять метод асинхронно. Однако он совместим только с методами, которые возвращают тип void или Future .

Давайте напишем метод displayFactorial , который асинхронно отображает факториал числа:

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

Затем мы перекомпилируем класс, чтобы позволить 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

Как видно из лога, библиотека создает отдельный поток демона jcabi-async для выполнения всех асинхронных операций .

Теперь воспользуемся аннотацией @Async для возврата экземпляра Future :

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

Если мы используем @Async для метода, который не возвращает void или Future , во время выполнения будет создано исключение, когда мы его вызовем.

4. @Кэшируемый

Аннотация @Cacheable позволяет кэшировать результаты метода, чтобы избежать дублирования вычислений.

Например, давайте напишем метод 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;
}

Здесь кэшированный результат будет иметь время жизни 2 секунды. Точно так же мы можем сделать результат кэшируемым навсегда, используя:

@Cacheable(forever = true)

Как только мы перекомпилируем класс и выполним его снова, библиотека зарегистрирует сведения о двух потоках демона, которые обрабатывают механизм кэширования:

[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

Когда мы вызываем наш метод cacheExchangeRates , библиотека кэширует результат и регистрирует детали выполнения:

[main] INFO com.foreach.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

Итак, при повторном вызове (в течение 2 секунд) cacheExchangeRates вернет результат из кеша:

[main] INFO com.foreach.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)

Если метод выдает исключение, результат не будет кэшироваться.

5. @Журналируемый

Библиотека предоставляет аннотацию @Loggable для простого ведения журнала с использованием средства ведения журнала SLF4J.

Давайте добавим аннотацию @Loggable к нашим методам displayFactorial и cacheExchangeRates :

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

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

Затем, после перекомпиляции, аннотация будет регистрировать имя метода, возвращаемое значение и время выполнения:

[main] INFO com.foreach.jcabi.JcabiAspectJ - #displayFactorial(): in 1.16ms
[main] INFO com.foreach.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

Как и в случае с @Loggable , мы можем использовать аннотацию @LogExceptions для регистрации только тех исключений, которые вызывает метод.

Давайте используем @LogExceptions в методеdivideByZero , который вызовет исключение ArithmeticException :

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

Выполнение метода зарегистрирует исключение, а также вызовет исключение:

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

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

7. @Тихо

Аннотация @Quietly похожа на @LogExceptions , за исключением того, что она не распространяет никаких исключений, созданных методом . Вместо этого он просто регистрирует их.

Давайте добавим аннотацию @Quietly к нашему методу DivisionByZero :

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

Следовательно, аннотация проглотит исключение и зарегистрирует только детали исключения, которое в противном случае было бы сгенерировано:

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

Аннотация @Quietly совместима только с методами, возвращающими тип void .

8. @RetryOnFailure

Аннотация @RetryOnFailure позволяет нам повторить выполнение метода в случае исключения или сбоя. ``

Например, добавим аннотацию @RetryOnFailure в наш метод DivisionByZero :

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

Таким образом, если метод выдает исключение, рекомендация АОП попытается выполнить его дважды:

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

Кроме того, мы можем определить другие параметры, такие как задержка , единица измерения и типы , при объявлении аннотации @RetryOnFailure :

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

В этом случае рекомендация AOP попытается выполнить метод трижды с задержкой в 5 секунд между попытками, только если метод выдает исключение NumberFormatException .

9. @UnitedThrow

Аннотация @UnitedThrow позволяет нам перехватывать все исключения, генерируемые методом, и заключать их в указанное нами исключение . Таким образом, он объединяет исключения, выдаваемые методом.

Например, давайте создадим метод processFile , который генерирует IOException и InterruptedException :

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

Здесь мы добавили аннотацию для переноса всех исключений в IllegalStateException . Поэтому при вызове метода трассировка стека исключения будет выглядеть так:

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

10. Заключение

В этой статье мы рассмотрели библиотеку Java jcabi-aspects .

Во-первых, мы увидели быстрый способ настроить библиотеку в нашем проекте Maven с помощью jcabi-maven-plugin .

Затем мы рассмотрели несколько удобных аннотаций, таких как @Async , @Loggable и @RetryOnFailure , которые изменяют поведение приложения Java с помощью АОП.

Как обычно, все реализации кода доступны на GitHub .