Введение в AspectJ

1. Вступление

Эта статья представляет собой быстрое и практическое введение в AspectJ.

Сначала мы покажем, как включить аспектно-ориентированное программирование, а затем сосредоточимся на разнице между компиляцией во время компиляции, после компиляции и во время загрузки.

Давайте начнем с краткого введения аспектно-ориентированного программирования (AOP) и основ AspectJ.

2. Обзор

АОП - это парадигма программирования, целью которой является повышение модульности за счет разделения сквозных задач. Это достигается путем добавления дополнительного поведения к существующему коду без изменения самого кода. Вместо этого мы объявляем отдельно, какой код нужно изменить.

AspectJ реализует как проблемы, так и переплетения проблем, используя расширения языка программирования Java.

3. Зависимости Maven

AspectJ предлагает разные библиотеки в зависимости от его использования. Мы можем найти зависимости Maven в группе org.aspectj в центральном репозитории Maven.

В этой статье мы сосредоточимся на зависимостях, необходимых для создания аспектов и Weaver с использованием ткачей времени компиляции, посткомпиляции и времени загрузки.

3.1. AspectJ Runtime

При запуске программы AspectJ путь к классам должен содержать классы и аспекты вместе с исполняющей библиотекой AspectJ aspectjrt.jar:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.9</version>
</dependency>

Эта зависимость доступна в Maven Central .

3.2. AspectJWeaver

Помимо зависимости AspectJ во время выполнения, нам также потребуется включить aspectjweaver.jar , чтобы представить рекомендации для класса Java во время загрузки:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

Эта зависимость также доступна по адресу Maven Central .

4. Создание Аспекта

AspectJ обеспечивает реализацию AOP и имеет три основных понятия:

  • Join Point

  • Pointcut

  • Совет

Мы продемонстрируем эти концепции, создав простую программу для проверки баланса учетной записи пользователя.

Во-первых, давайте создадим класс Account с заданным балансом и методом для вывода:

public class Account {
    int balance = 20;

    public boolean withdraw(int amount) {
        if (balance < amount) {
            return false;
        }
        balance = balance - amount;
        return true;
    }
}

Мы создадим файл AccountAspect.aj для регистрации информации об учетной записи и проверки баланса учетной записи (обратите внимание, что файлы AspectJ заканчиваются расширением файла « .aj »):

public aspect AccountAspect {
    final int MIN__BALANCE = 10;

    pointcut callWithDraw(int amount, Account acc) :
     call(boolean Account.withdraw(int)) && args(amount) && target(acc);

    before(int amount, Account acc) : callWithDraw(amount, acc) {
    }

    boolean around(int amount, Account acc) :
      callWithDraw(amount, acc) {
        if (acc.balance < amount) {
            return false;
        }
        return proceed(amount, acc);
    }

    after(int amount, Account balance) : callWithDraw(amount, balance) {
    }
}

Как мы видим, мы добавили pointcut к методу изъятия и создали три advises , которые ссылаются на определенный pointcut .

Чтобы понять следующее, мы вводим следующие определения:

  • Аспект : Модуляризация проблемы, которая охватывает несколько

объекты. Каждый аспект фокусируется на определенной функциональности Точка соединения ** : точка во время выполнения скрипта, такого как

выполнение метода или доступа к свойству Совет ** : действие, предпринимаемое аспектом в определенной точке соединения

  • Pointcut : регулярное выражение, соответствующее точкам соединения. Совет

связан с выражением pointcut и выполняется в любой точке соединения, которая соответствует pointcut

Для получения более подробной информации об этих концепциях и их конкретной семантике мы можем обратиться к следующей link

Далее нам нужно втирать аспекты в наш код. В следующих разделах рассматриваются три различных типа ткачества: ткачество во время компиляции, ткачество после компиляции и ткачество во время загрузки в AspectJ.

5. Время компиляции

Самый простой подход к ткачеству - ткачество во время компиляции. Когда у нас есть как исходный код аспекта, так и код, в котором мы используем аспекты, компилятор AspectJ скомпилирует из исходного кода и выдаст файлы тканых классов в качестве вывода. Затем, после выполнения вашего кода, выходной класс процесса ткачества загружается в JVM как обычный класс Java.

Мы можем загрузить AspectJ Инструменты разработки , так как он включает в себя компилятор AspectJ. Одной из наиболее важных функций AJDT является инструмент для визуализации сквозных задач, который полезен для отладки спецификации pointcut. Мы можем визуализировать комбинированный эффект даже до развертывания кода.

Мы используем плагин Mopect AspectJ Maven для добавления аспектов AspectJ в наши классы с помощью компилятора AspectJ.

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.7</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <showWeaveInfo>true</showWeaveInfo>
        <verbose>true</verbose>
        <Xlint>ignore</Xlint>
        <encoding>UTF-8 </encoding>
    </configuration>
    <executions>
        <execution>
            <goals>
                <!-- use this goal to weave all your main classes -->
                <goal>compile</goal>
                <!-- use this goal to weave all your test classes -->
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Для получения более подробной информации о параметрах компилятора AspectJ, мы можем захотеть проверить следующее http://www.mojohaus.org/aspectj-maven-plugin/ajc reference/standard opts.html[link].

Давайте добавим несколько тестов для нашего класса Account:

public class AccountTest {
    private Account account;

    @Before
    public void before() {
        account = new Account();
    }

    @Test
    public void given20AndMin10__whenWithdraw5__thenSuccess() {
        assertTrue(account.withdraw(5));
    }

    @Test
    public void given20AndMin10__whenWithdraw100__thenFail() {
        assertFalse(account.withdraw(100));
    }
}

Когда мы запускаем контрольные примеры, приведенный ниже текст, отображаемый в консоли, означает, что мы успешно создали исходный код:

----[INFO]Join point 'method-call
(boolean com.baeldung.aspectj.Account.withdraw(int))' in Type
'com.baeldung.aspectj.test.AccountTest' (AccountTest.java:20)
advised by around advice from 'com.baeldung.aspectj.AccountAspect'
(AccountAspect.class:18(from AccountAspect.aj))
[INFO]Join point 'method-call
(boolean com.baeldung.aspectj.Account.withdraw(int))' in Type
'com.baeldung.aspectj.test.AccountTest' (AccountTest.java:20)
advised by before advice from 'com.baeldung.aspectj.AccountAspect'
(AccountAspect.class:13(from AccountAspect.aj))
[INFO]Join point 'method-call
(boolean com.baeldung.aspectj.Account.withdraw(int))' in Type
'com.baeldung.aspectj.test.AccountTest' (AccountTest.java:20)
advised by after advice from 'com.baeldung.aspectj.AccountAspect'
(AccountAspect.class:26(from AccountAspect.aj))

2016-11-15 22:53:51[main]INFO com.baeldung.aspectj.AccountAspect - Balance before withdrawal: 20 2016-11-15 22:53:51[main]INFO com.baeldung.aspectj.AccountAspect - Withdraw ammout: 5 2016-11-15 22:53:51[main]INFO com.baeldung.aspectj.AccountAspect - Balance after withdrawal : 15 2016-11-15 22:53:51[main]INFO com.baeldung.aspectj.AccountAspect - Balance before withdrawal: 20 2016-11-15 22:53:51[main]INFO com.baeldung.aspectj.AccountAspect - Withdraw ammout: 100 2016-11-15 22:53:51[main]INFO com.baeldung.aspectj.AccountAspect - Withdrawal Rejected! 2016-11-15 22:53:51[main]INFO com.baeldung.aspectj.AccountAspect - Balance after withdrawal : 20

===  **  6. Посткомпиляция ткачества **

Плетение после компиляции (также иногда называемое бинарным переплетением) используется для переплетения существующих файлов классов и файлов JAR. Как и в случае ткачества во время компиляции, аспекты, используемые для ткачества, могут быть в исходной или двоичной форме и сами могут быть сплетены аспектами.

Чтобы сделать это с плагином Mopect AspectJ Maven, нам нужно настроить все JAR-файлы, которые мы хотели бы сплести в конфигурации плагина:

[source,xml,gutter:,true]

<configuration> <weaveDependencies> <weaveDependency> <groupId>org.agroup</groupId> <artifactId>to-weave</artifactId> </weaveDependency> <weaveDependency> <groupId>org.anothergroup</groupId> <artifactId>gen</artifactId> </weaveDependency> </weaveDependencies> </configuration>

JAR-файлы, содержащие классы для переплетения, должны быть перечислены как `<dependencies/>` в проекте Maven и перечислены как `<weaveDependencies/>` в `<configuration>` плагина AspectJ Maven.

===  **  7. Время ткачества **

Плетение времени загрузки - это просто бинарное ткачество, отложенное до момента, когда загрузчик классов загружает файл класса и определяет класс для JVM.

Для этого требуется один или несколько «ткацких загрузчиков классов». Они либо предоставляются явным образом средой выполнения, либо включаются с помощью «ткацкого агента».

====  **  7.1. Включение ткачества времени загрузки **

Плетение времени загрузки AspectJ можно включить с помощью агента AspectJ, который может участвовать в процессе загрузки классов и переплетать любые типы до того, как они будут определены в ВМ. Мы указываем параметр __javaagent__ для JVM __`-javaagent: pathto/aspectjweaver.jar`__ или с помощью плагина Maven для настройки __javaagent__:

[source,xml,gutter:,true]

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.10</version> <configuration> <argLine> -javaagent:"${settings.localRepository}"/org/aspectj/ aspectjweaver/${aspectj.version}/ aspectjweaver-${aspectj.version}.jar </argLine> <useSystemClassLoader>true</useSystemClassLoader> <forkMode>always</forkMode> </configuration> </plugin>

====  **  7.2. Конфигурация Weaver **

Ткацкий агент времени загрузки AspectJ настраивается с помощью файлов __aop.xml__. Он ищет один или несколько файлов __aop.xml__ в пути к классам в каталоге __META-INF__ и объединяет содержимое для определения конфигурации ткача.

Файл __aop.xml__ содержит два ключевых раздела:

**  **  Аспекты ** : определяет один или несколько аспектов для ткача и элементов управления

какие аспекты должны использоваться в процессе ткачества. __Aspect__
элемент может дополнительно содержать один или несколько __include__ и __exclude__
элементы (по умолчанию все определенные аспекты используются для плетения)
**  **  Weaver ** : определяет параметры ткача для ткача и задает набор

типов, которые должны быть сотканы. Если элементы включения не указаны, то все типы, видимые для ткача, будут сотканы

Давайте настроим аспект для ткача:

[source,xml,gutter:,true]

<aspectj> <aspects> <aspect name="com.baeldung.aspectj.AccountAspect"/> <weaver options="-verbose -showWeaveInfo"> <include within="com.baeldung.aspectj.** "/> </weaver> </aspects> </aspectj>

Как мы видим, мы настроили аспект, который указывает на __AccountAspect__, и AspectJ будет сплетать только исходный код в пакете __com.baeldung.aspectj__.

===  **  8. Аннотирующие аспекты **

В дополнение к знакомому стилю объявления аспектов в AspectJ, AspectJ 5 также поддерживает стиль объявления аспектов на основе аннотаций. Мы неофициально называем набор аннотаций, поддерживающих этот стиль разработки, аннотациями «__ @ AspectJ__».

Давайте создадим аннотацию:

[source,java,gutter:,true]

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Secured { public boolean isLocked() default false; }

Мы используем аннотацию __ @ Secured__, чтобы включить или отключить метод:

[source,java,gutter:,true]

public class SecuredMethod {

@Secured(isLocked = true)
public void lockedMethod() {
}
    @Secured(isLocked = false)
    public void unlockedMethod() {
    }
}
Затем мы добавляем аспект, используя стиль аннотации AspectJ, и проверяем разрешение на основе атрибута аннотации @Secured:

[source,java,gutter:,true]

@Aspect public class SecuredMethodAspect { @Pointcut("@annotation(secured)") public void callAt(Secured secured) { }

    @Around("callAt(secured)")
    public Object around(ProceedingJoinPoint pjp,
      Secured secured) throws Throwable {
        return secured.isLocked() ? null : pjp.proceed();
    }
}
Для более подробной информации о стиле аннотаций AspectJ мы можем проверить следующую ссылку://eclipse.org/aspectj/doc/released/adk15notebook/annotations-aspectmembers.html[ссылка].

Затем мы сплетаем наш класс и аспект с помощью ткача во время загрузки и помещаем __aop.xml__ в папку __META-INF__:

[source,xml,gutter:,true]

<aspectj> <aspects> <aspect name="com.baeldung.aspectj.SecuredMethodAspect"/> <weaver options="-verbose -showWeaveInfo"> <include within="com.baeldung.aspectj.** "/> </weaver> </aspects> </aspectj>

Наконец, мы добавляем модульный тест и проверяем результат:

[source,java,gutter:,true]

@Test public void testMethod() throws Exception { SecuredMethod service = new SecuredMethod(); service.unlockedMethod(); service.lockedMethod(); }

Когда мы запускаем тестовые случаи, мы можем проверить вывод консоли, чтобы убедиться, что мы успешно вплетали наш аспект и класс в исходный код:

[source,java,gutter:,true]

----[INFO]Join point 'method-call
(void com.baeldung.aspectj.SecuredMethod.unlockedMethod())'
in Type 'com.baeldung.aspectj.test.SecuredMethodTest'
(SecuredMethodTest.java:11)
advised by around advice from 'com.baeldung.aspectj.SecuredMethodAspect'
(SecuredMethodAspect.class(from SecuredMethodAspect.java))

2016-11-15 22:53:51[main]INFO com.baeldung.aspectj.SecuredMethod
- unlockedMethod
2016-11-15 22:53:51[main]INFO c.b.aspectj.SecuredMethodAspect -
public void com.baeldung.aspectj.SecuredMethod.lockedMethod() is locked

9. Заключение

В этой статье мы рассмотрели вводные понятия об AspectJ. Для получения дополнительной информации вы можете посмотреть на домашней странице AspectJ.

Вы можете найти исходный код этой статьи over на GitHub .