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 FuturegetFactorial(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 .