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 .