Иногда я хочу регистрировать (через slf4j и log4j ) каждое выполнение метода, видеть, какие аргументы он получает, что он возвращает и сколько времени занимает каждое выполнение. Вот как я делаю это с помощью AspectJ , jcabi-aspect и Java 6 аннотации:
|
1
2
3
4
5
6
|
public class Foo { @Loggable public int power(int x, int p) { return Math.pow(x, p); }} |
Вот что я вижу в выводе log4j:
|
1
2
|
[INFO] com.example.Foo #power(2, 10): 1024 in 12μs[INFO] com.example.Foo #power(3, 3): 27 in 4μs |
Хорошо, не правда ли? Теперь посмотрим, как это работает.
Аннотация с сохранением во время выполнения
Аннотации — это техника, представленная в Java 6. Это инструмент метапрограммирования, который не меняет способ работы кода, но ставит отметки для определенных элементов (методов, классов или переменных). Другими словами, аннотации — это просто маркеры, прикрепленные к коду, которые можно увидеть и прочитать. Некоторые аннотации предназначены для просмотра только во время компиляции — они не существуют в файлах .class после компиляции. Другие остаются видимыми после компиляции и могут быть доступны во время выполнения.
Например, @Override относится к первому типу (его тип хранения — SOURCE ), а @Test из JUnit — ко второму типу (тип хранения — RUNTIME ). @Loggable — тот, который я использую в приведенном выше сценарии — это аннотация второго типа, из аспектов jcabi . После компиляции он остается с байт-кодом в файле .class .
Опять же, важно понимать, что хотя метод power() аннотирован и скомпилирован, он пока ничего не отправляет в slf4j. Он просто содержит маркер с надписью «пожалуйста, запишите мою казнь».
Аспектно-ориентированное программирование (АОП)
AOP — это полезный метод, который позволяет добавлять исполняемые блоки в исходный код без явного его изменения. В нашем примере мы не хотим регистрировать выполнение метода внутри класса. Вместо этого мы хотим, чтобы какой-то другой класс перехватывал каждый вызов метода power() , измерял время его выполнения и отправлял эту информацию в slf4j.
Мы хотим, чтобы этот перехватчик понимал нашу аннотацию @Loggable и регистрировал каждый вызов этого конкретного метода power() . И, конечно же, тот же перехватчик следует использовать для других методов, где мы разместим такую же аннотацию в будущем.
Этот случай идеально соответствует первоначальному замыслу AOP — чтобы избежать повторения некоторых общих действий в нескольких классах.
Ведение журнала является дополнительной функцией к нашей основной функции, и мы не хотим загрязнять наш код несколькими инструкциями по ведению журнала. Вместо этого мы хотим, чтобы регистрация происходила негласно.
С точки зрения AOP наше решение можно объяснить как создание аспекта, который пересекает код в определенных точках соединения и применяет рекомендации, которые реализуют желаемую функциональность.
AspectJ
Давайте посмотрим, что означают эти волшебные слова. Но, во-первых, давайте посмотрим, как jcabi-аспекты реализуют их с помощью AspectJ (это упрощенный пример, полный код которого вы можете найти в MethodLogger.java ):
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
@Aspectpublic class MethodLogger { @Around("execution(* *(..)) && @annotation(Loggable)") public Object around(ProceedingJoinPoint point) { long start = System.currentTimeMillis(); Object result = point.proceed(); Logger.info( "#%s(%s): %s in %[msec]s", MethodSignature.class.cast(point.getSignature()).getMethod().getName(), point.getArgs(), result, System.currentTimeMillis() - start ); return result; }} |
Это аспект с одним советом around() внутри. Аспект @Aspect а совет — @Around . Как обсуждалось выше, эти аннотации являются просто маркерами в файлах .class . Они ничего не делают, кроме как предоставляют метаинформацию тем, кто интересуется средой выполнения.
Аннотация @Around имеет один параметр, который — в данном случае — говорит, что рекомендация должна применяться к методу, если:
- его модификатор видимости
*(public,protectedилиprivate); - его имя — имя
*(любое имя); - его аргументы
..(любые аргументы); и - это аннотировано
@Loggable
Когда вызов аннотированного метода должен быть перехвачен, метод around() выполняется перед выполнением фактического метода. Когда вызов метода power() должен быть перехвачен, метод around() получает экземпляр класса ProceedingJoinPoint и должен возвращать объект, который будет использоваться в результате метода power() .
Чтобы вызвать оригинальный метод power() , совет должен вызвать proceed() объекта точки соединения .
Мы компилируем этот аспект и делаем его доступным в classpath вместе с нашим основным файлом Foo.class . Пока все хорошо, но нам нужно сделать последний шаг, чтобы претворить в жизнь наш аспект — мы должны применить наш совет.
Бинарный аспект плетения
Аспект плетения — это название процесса применения рекомендаций. Аспект ткач изменяет оригинальный код, вводя вызовы аспектов. AspectJ делает именно это. Мы даем ему два двоичных Java-класса Foo.class и MethodLogger.class ; он возвращает три модифицированных Foo.class , Foo$AjcClosure1.class и неизмененный MethodLogger.class .
Чтобы понять, какие советы и какие методы следует применять, AspectJ ткач использует аннотации из файлов .class . Кроме того, он использует отражение для просмотра всех классов на пути к классам. Он анализирует, какие методы удовлетворяют условиям из аннотации @Around . Конечно, он находит наш метод power() .
Итак, есть два шага. Сначала мы компилируем наши файлы .java с использованием javac и получаем два файла. Затем AspectJ переплетает / модифицирует их и создает свой собственный дополнительный класс. Наш класс Foo выглядит примерно так после плетения:
|
01
02
03
04
05
06
07
08
09
10
|
public class Foo { private final MethodLogger logger; @Loggable public int power(int x, int p) { return this.logger.around(point); } private int power_aroundBody(int x, int p) { return Math.pow(x, p); }} |
Ткач AspectJ перемещает нашу первоначальную функциональность в новый метод power_aroundBody() и перенаправляет все вызовы power() в аспектный класс MethodLogger .
Вместо одного метода power() в классе Foo теперь у нас есть четыре класса, работающих вместе. Отныне это происходит за кулисами при каждом обращении к power() :
Оригинальная функциональность метода power() обозначена маленькой зеленой линией жизни на диаграмме.
Как видите, процесс создания аспектов связывает воедино классы и аспекты, передавая вызовы между ними через точки соединения. Без переплетения и классы, и аспекты являются просто скомпилированными двоичными файлами Java с прикрепленными аннотациями.
jcabi-аспекты
jcabi-aspect — это библиотека JAR, которая содержит аннотацию MethodLogger аспект MethodLogger (кстати, есть еще много аспектов и аннотаций). Вам не нужно писать свой собственный аспект для регистрации методов. Просто добавьте несколько зависимостей в ваш classpath и настройте jcabi-maven-plugin для создания аспектов (получите их последние версии в Maven Central ):
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<project> <depenencies> <dependency> <dependency> <groupId>com.jcabi</groupId> <artifactId>jcabi-aspects</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> </dependency> </dependency> </depenencies> <build> <plugins> <plugin> <groupId>com.jcabi</groupId> <artifactId>jcabi-maven-plugin</artifactId> <executions> <execution> <goals> <goal>ajc</goal> </goals> </execution> </executions> </plugin> </plugins> </build></project> |
Поскольку эта процедура плетения требует больших усилий по настройке, я создал удобный плагин Maven с целью ajc , который выполняет всю работу по плетению аспектов. Вы можете использовать AspectJ напрямую, но я рекомендую использовать jcabi-maven-plugin .
Вот и все. Теперь вы можете использовать аннотацию @com.jcabi.aspects.Loggable и ваши методы будут регистрироваться через slf4j.
Если что-то не работает, как объяснено, не стесняйтесь представить вопрос Github .
Похожие сообщения
Вы также можете найти эти сообщения интересными:
- Как повторить вызов метода Java для исключения
- Кэширование результатов метода Java
- Избавьтесь от статических логгеров Java
- Ограничить время выполнения Java-метода
- Простой Java SSH клиент
| Ссылка: | Регистрация методов Java с помощью AOP и аннотаций от нашего партнера по JCG Егора Бугаенко в блоге About Programming . |
