Pull to refresh

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

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

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

В теге
 следует указать все классы к которым будут применены аспекты + пакет со всеми аспектами.

Проблема № 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. В теге файла app.context нужно указывать как сами “weav” классы так и aspect-ы.
    CTW и LTW не совместимые технологии.
Tags:
Hubs:
+6
Comments 8
Comments Comments 8

Articles