AOP in action. AspectJ (CTW) + Spring + LTW

от автора

Решил внедрить АОП логирование на проект и не внедрил. Как и почему, собственно и хочу поделиться.

Я не буду описывать суть и принципы АОП, а опишу только те проблемы, с которыми я столкнулся, и решения которых заняло много времени.
У меня было в распоряжении 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, я решил использовать именно его. Цена вопроса:

  1. Теперь у нас нет одного .xml файла, куда мы красиво складываем наши pointcut-ы, aspect-ы.
  2. Нужно добавить новый aop.xml, где мы должны указать наши weaver-ы (классы которые непосредственно учувствуют в процессе), aspects-ы.
  3. Pointcut-ы тепер указываются непосредственно над aspect-ами.
    @Before( "execution(*  com.solutions. web.test.WebTestClass.testA())")     public void testALog() {} 
  4. Над классами аспектов появляется аннотация @Aspect.
  5. Нужно добавить аргумент при запуске 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. Цена вопроса:

  1. Больше нет хоть какого-то конфигурационного файла, где мы можем посмотреть все наши aspect-ы и pointcut-ы.
  2. Cкладывая АОП систему логирования в один пакет можно найти все pointcut-ы, но все равно это неудобно и занимает много времени.
  3. Нужно использовать 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.

ИТОГО

Что я для себя вынес и хотел бы добавить:

  1. Для всех public методов верхнего уровня (EAR/APP, WEB уровень) можно использовать Spring AOP.
  2. Для всего WEB уровня не public и методов не верхнего уровня можно использовать LTW (если СTW не используется).
  3. Для всего APP уровня не public и методов не верхнего уровня можно использовать CTW (если LTW не используется).
  4. В теге <weaver> файла app.context нужно указывать как сами “weav” классы так и aspect-ы.
  5. 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 

Литература

Список литературы:

  1. Spring AOP.
  2. AspectJ.
  3. AspectJ+Spring, LTW.
  4. Spring3-AOP-AspectJ-XML-Example.

Cсылки на статьи из Хабра:

  1. AspectJ, Spring, Maven.
  2. Знакомство с АОП.

ссылка на оригинал статьи http://habrahabr.ru/post/186940/