The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:
Иногда мне хочется записывать (через slf4j и log4j) каждое выполнение метода, видеть, какие аргументы он получает, что он возвращает и сколько времени занимает каждое выполнение. Вот как я делаю это с помощью AspectJ, jcabi-aspects и аннотаций Java 6.
Вот что я вижу в выводе log4j:
Приятно, не так ли? Теперь давайте посмотрим, как это работает.
Аннотации - это техника, введенная в Java 6. Это метапрограммный инструмент, который не изменяет работу кода, но дает отметки определенным элементам (методам, классам или переменным). Другими словами, аннотации - это просто метки, прикрепленные к коду, которые можно увидеть и прочитать. Некоторые аннотации предназначены только для компиляции - они не существуют в файлах .class
после компиляции. Другие остаются видимыми после компиляции и могут быть доступны во время выполнения.
Например, @Override
относится к первому типу (его тип сохранения SOURCE
), в то время как @Test
из JUnit относится ко второму типу (тип сохранения RUNTIME
). @Loggable
- аннотация второго типа, которую я использую в приведенном выше скрипте, из jcabi-aspects. Она остается с байт-кодом в файле .class
после компиляции.
Опять же, важно понимать, что хотя метод power()
аннотирован и скомпилирован, он до сих пор не отправляет ничего в slf4j. Он просто содержит маркер, который говорит “пожалуйста, зарегистрируйте мое выполнение”.
AOP - это полезная техника, которая позволяет добавлять исполняемые блоки в исходный код без явного изменения его. В нашем примере мы не хотим регистрировать выполнение метода внутри класса. Вместо этого мы хотим, чтобы другой класс перехватывал каждый вызов метода power()
, измерял время его выполнения и отправлял эту информацию в slf4j.
Мы хотим, чтобы перехватчик понимал нашу аннотацию @Loggable
и регистрировал каждый вызов этого конкретного метода power()
. И, конечно же, этот же перехватчик должен использоваться для других методов, где мы будем размещать ту же аннотацию в будущем.
Этот случай идеально соответствует первоначальному намерению AOP - избежать повторной реализации некоторого общего поведения в нескольких классах.
Регистрация является дополнительной функцией нашей основной функциональности, и мы не хотим загромождать наш код множеством инструкций регистрации. Вместо этого мы хотим, чтобы регистрация происходила в фоновом режиме.
С точки зрения AOP, наше решение может быть объяснено как создание аспекта, который пересекает код в определенных точках соединения и применяет совет типа around, реализующий необходимую функциональность.
Давайте посмотрим, что означают эти волшебные слова. Но сначала давайте посмотрим, как jcabi-aspects реализует их с использованием AspectJ (это упрощенный пример, полный код вы можете найти в MethodLogger.java
).
Это аспект с единственным советом вокруг around()
. Аспект аннотирован с помощью @Aspect
, а совет аннотирован с помощью @Around
. Как обсуждалось ранее, эти аннотации являются всего лишь маркерами в файлах .class
. Они ничего не делают, кроме предоставления некоторой мета-информации тем, кто интересуется временем выполнения.
Аннотация @Around
имеет один параметр, который в данном случае указывает, что совет должен быть применен к методу, если:
его имя
*
(любое имя);его аргументы -
..
(любые аргументы); ион аннотирован с помощью
@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
. Оно также использует reflection, чтобы просмотреть все классы в класспасе. Оно анализирует, какие методы удовлетворяют условиям аннотации @Around
. Конечно же, оно находит наш метод power()
.
Итак, есть два шага. Сначала мы компилируем наши файлы .java
с помощью javac
и получаем два файла. Затем AspectJ осуществляет сплетение/изменение и создает свой собственный дополнительный класс. Наш класс Foo
выглядит примерно так после сплетения:
Аспектный средство AspectJ перемещает нашу исходную функциональность в новый метод power_aroundBody()
и перенаправляет все вызовы power()
в аспектный класс MethodLogger
.
Вместо одного метода power()
в классе Foo
теперь у нас есть четыре класса, работающих вместе. Теперь это происходит за кулисами при каждом вызове power()
:
hide footbox skinparam sequence { ArrowFontName “Courier New” ArrowFontSize 17 ParticipantFontName “Courier New” ParticipantFontSize 17 ActorFontSize 17 }
actor “Client” as client participant “Foo” as foo participant “MethodLogger” as aspect participant “JoinPoint” as point participant “Foo$AjcClosure1” as closure
client -> foo : power() activate foo foo -> aspect : around() activate aspect aspect -> point : proceed() activate point point -> closure : run() activate closure closure -> foo : power_aroundBody() activate foo #228b22 foo –> closure deactivate foo closure –> point deactivate closure point –> aspect deactivate point aspect –> foo deactivate aspect foo –> client deactivate foo
Исходная функциональность метода power()
обозначена маленькой зеленой линией на диаграмме.
Как видите, процесс аспектного включения соединяет вместе классы и аспекты, передавая вызовы между ними через точки соединения. Без включения, как классы, так и аспекты являются просто скомпилированными двоичными файлами Java с прикрепленными аннотациями.
jcabi-aspects - это JAR-библиотека, которая содержит аннотацию Loggable
и аспект MethodLogger
(кстати, есть еще много аспектов и аннотаций). Вам не нужно писать свой собственный аспект для логирования методов. Просто добавьте несколько зависимостей в ваш classpath и настройте jcabi-maven-plugin для слияния аспектов (получите их последние версии в Maven Central).
Поскольку этот процесс вязки требует значительных усилий для настройки, я создал удобный плагин Maven с целью ajc
, который выполняет всю работу по вязке аспектов. Вы можете использовать AspectJ напрямую, но я рекомендую использовать jcabi-maven-plugin.
Вот и все. Теперь вы можете использовать аннотацию @com.jcabi.aspects.Loggable
, и ваши методы будут логироваться через slf4j.
Если что-то не работает, как объяснялось, не стесняйтесь отправить проблему на GitHub.
Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-27 at 10:26