Иногда я хочу регистрировать (через 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
|
@Aspect public 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 . |