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

в 7:58, , рубрики: aop, java, maven, spring, Weblogic, Программирование, разработка, метки: , , , ,

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

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

Автор: Liroyd

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js