Рубрики
Без рубрики

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

Узнайте о библиотеке Java jcabi-aspects, коллекции удобных аннотаций, которые изменяют поведение Java-приложения с помощью аспектно-ориентированного программирования

Автор оригинала: baeldung.

1. Обзор

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

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

2. Настройка

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


    com.jcabi
    jcabi-aspects
    0.22.6

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


    org.aspectj
    aspectjrt
    1.9.2
    runtime

Далее, давайте добавим jcabi-maven-plugin плагин, который сплетает двоичные файлы с аспектами AspectJ во время компиляции . Плагин предоставляет цель ajc , которая выполняет автоматическое плетение:


    com.jcabi
    jcabi-maven-plugin
    0.14.1
    
        
            
                ajc
            
        
    
    
        
            org.aspectj
            aspectjtools
            1.9.2
        
        
            org.aspectj
            aspectjweaver
            1.9.2
        
    

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

mvn clean package

Журналы, сгенерированные jcabi-maven-плагином при компиляции, будут выглядеть следующим образом:

[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

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

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

@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 getFactorial(int number) {
    Future factorialFuture = CompletableFuture.supplyAsync(() -> factorial(number));
    return factorialFuture;
}

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

4. @Cacheable

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

Например, давайте напишем метод cache Exchange Rates , который возвращает последние обменные курсы:

@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

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

[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

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

[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)

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

5. @Loggable

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

Давайте добавим @Loggable аннотацию к нашим факториалам отображения и обменным курсам кэша методам:

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

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

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

[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

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

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

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

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

[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 похожа на @LogExceptions , за исключением того, что она не распространяет никаких исключений, вызванных методом . Вместо этого он просто регистрирует их.

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

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

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

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

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

8. @RetryOnFailure

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

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

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

Таким образом, если метод вызывает исключение, совет 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

Кроме того, мы можем определить другие параметры, такие как delay , unit и types , при объявлении аннотации @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("baeldung.txt"));
    reader.readLine();
    // additional file processing
}

Здесь мы добавили аннотацию, чтобы обернуть все исключения в Исключение 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. Заключение

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

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

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

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