Решил внедрить АОП логирование на проект и не внедрил. Как и почему, собственно и хочу поделиться.
Я не буду описывать суть и принципы АОП, а опишу только те проблемы, с которыми я столкнулся, и решения которых заняло много времени.
У меня было в распоряжении Spring, WebLogic, google.com и проект, куда я хотел внедрить АОП логирование. Скажу сразу, до этого я никогда не работал с АОП.
Проблема № 1
Spring AOP – использует proxy-based подход.
Если у нас есть класс (СlassA) с методами (methodA, methodB), при этом methodB() вызывает methodA() и аспект (допустим after) который должен выполняться при вызове methodA():
public class ClassA {
public void methodA() {
System.out.println("methodA");
}
public void methodB() {
System.out.println("methodB");
methodA();
}
}
public class AspectClass {
public void aspectMethodA() {
System.out.println("Aspect on method A");
}
}
И некий класс который в рамках какой-то логики делает вызов этих методов:
public void execute() {
// .....
classA.methodA();
classA.methodB();
// .....
}
Результат такого вызова (используя стандартный Spring AOP) будет:
methodA
Aspect on method A
methodB
methodA
И все, второй раз аспект не сработает. В документации хорошо описан принцип работы Spring-AOP, прочитав его, все встает на свои места. Это отправная точка.
Проблема № 2
Методы должны быть public. Тут без комментариев.
Так вот, почитав документацию и другую познавательную литературу я нашел следующее решение:
- Load-time weaving (LTW).
- Compile-time weaving (CTW).
Поскольку я нашел хорошую документацию по LTW, я решил использовать именно его. Цена вопроса:
- Теперь у нас нет одного .xml файла, куда мы красиво складываем наши pointcut-ы, aspect-ы.
- Нужно добавить новый aop.xml, где мы должны указать наши weaver-ы (классы которые непосредственно учувствуют в процессе), aspects-ы.
- Pointcut-ы тепер указываются непосредственно над aspect-ами.
@Before( "execution(* com.solutions. web.test.WebTestClass.testA())") public void testALog() {}
- Над классами аспектов появляется аннотация
@Aspect
. - Нужно добавить аргумент при запуске JM/WebLogic:
-javaagent:${PATH_TO_LIB }/aspectjweaver.jar
Примечание
Если посмотреть на пример приведенный в документации (aop.context):
<weaver>
<include within="foo.*"/>
</weaver>
<aspects>
<aspect name="foo.ProfilingAspect"/>
</aspects>
Да все работает, но одно НО — мы редко будем хранить наш выполняющий код и непосредственно код аспектов в одном классе/пакете. Эту маленькую деталь они упустили в описании. Так вот, если у нас есть класс (ClassA) и аспект (AspectA) которые находятся в разных пакетах, то валидной конфигурацией будет следующий aop.xml:
<weaver>
<include within="com.example.ClassA"/> <!-- путь к конкретному классу -->
<include within="com.log.* "/> <!—путь к пакету с аспектами>
</weaver>
<aspects>
<aspect name="com.log.AspectA"/>
</aspects>
В теге <weaver>
следует указать все классы к которым будут применены аспекты + пакет со всеми аспектами.
Проблема № 3
LTW нельзя применить на EAR/APP уровне.
«As Costin said, there is unfortunately nothing we can do about this. Load-time weaving only works for specific deployment units such as WARs, and even there it is considered an advanced feature that won't work in all runtime environments.”
Конкретно этот комментарий я искал очень долго.
Решением этой проблемы как вы догадались, и является использование CTW. Цена вопроса:
- Больше нет хоть какого-то конфигурационного файла, где мы можем посмотреть все наши aspect-ы и pointcut-ы.
- Cкладывая АОП систему логирования в один пакет можно найти все pointcut-ы, но все равно это неудобно и занимает много времени.
- Нужно использовать ajc-компайлер, соответственно подключать его к сборщикам проекта (ant, maven, gradle…).
Проблема № 4
CTW+LTW не совместимые технологии.
Может мне просто не повезло, но по не известным мне причинам LTW сканировало весь classpath и при вызове классов скомпилированных при помощи СTW падало с ошибкой:
java.lang.Exception: java.lang.NoSuchMethodError: com.aop.example.log.AspectA.aspectOf()Lcom/aop/example/log/AspectA;
Проблема сразу же пропадает после отключения LTW.
ИТОГО
Что я для себя вынес и хотел бы добавить:
- Для всех public методов верхнего уровня (EAR/APP, WEB уровень) можно использовать Spring AOP.
- Для всего WEB уровня не public и методов не верхнего уровня можно использовать LTW (если СTW не используется).
- Для всего APP уровня не public и методов не верхнего уровня можно использовать CTW (если LTW не используется).
- В теге
<weaver>
файла app.context нужно указывать как сами “weav” классы так и aspect-ы. - CTW и LTW не совместимые технологии.
В конечном итоге для приложения, имеющего WEB и APP уровни, получаем проект с Spring AOP + CTW технологиями.
От красивой конфигурации в одном .xml файле не осталось и следа. Из-за специфики проекта, объяснять все это заказчику я не решился и оставил эту затею для следующего проекта.
Проект
Скачать проект-пример можно из github-a. Проект на maven-e.
Класс Executor является spring-bean-ом, получив его экземпляр, выполните метод execute().
Если вы все правильно завели, то в результате должны получить следующие сообщения:
--methodA-- method
Aspect Before for methodCTW
--methodCTW-- method
Aspect After for method A
--webMethodA-- method
Web Aspect Before for MethodCTW
--webMethodForCTW-- method
Web Aspect After web method A
Литература
Список литературы:
Cсылки на статьи из Хабра:
Автор: Liroyd