Перейти к основному содержимому

Введение в AspectJ

· 9 мин. чтения

1. Введение

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

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

Начнем с краткого введения в аспектно-ориентированное программирование (АОП) и основ AspectJ.

2. Обзор

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

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

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

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

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

3.1. Время выполнения AspectJ

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

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

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

3.2. АспектДж.Вивер

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

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

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

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

AspectJ обеспечивает реализацию АОП и имеет три основные концепции:

  • Точка присоединения
  • Точечная резка
  • Совет

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

Во-первых, давайте создадим класс 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 к методу изъятия и создали три совета , которые ссылаются на определенный pointcut .

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

  • Аспект : модульность проблемы, которая охватывает несколько объектов. Каждый аспект фокусируется на определенной сквозной функциональности
  • Точка присоединения : точка во время выполнения скрипта, например, выполнение метода или доступ к свойству.
  • Совет : действие, предпринятое аспектом в определенной точке соединения.
  • Pointcut : регулярное выражение, соответствующее точкам соединения. Совет связан с выражением pointcut и запускается в любой точке соединения, которая соответствует pointcut.

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

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

5. Плетение во время компиляции

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

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

Мы используем подключаемый модуль AspectJ Maven от Mojo для включения аспектов 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, мы можем проверить следующую ссылку .

Давайте добавим несколько тестовых случаев для нашего класса 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.foreach.aspectj.Account.withdraw(int))' in Type
'com.foreach.aspectj.test.AccountTest' (AccountTest.java:20)
advised by around advice from 'com.foreach.aspectj.AccountAspect'
(AccountAspect.class:18(from AccountAspect.aj))

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

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

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

6. Плетение после компиляции

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

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

<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 :

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</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. Конфигурация Вивер

Связывающий агент AspectJ во время загрузки настраивается с использованием файлов aop.xml . Он ищет один или несколько файлов aop.xml в пути к классам в каталоге META-INF и объединяет содержимое для определения конфигурации weaver.

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

  • Аспекты : определяет один или несколько аспектов для ткача и контролирует, какие аспекты должны использоваться в процессе плетения. Элемент аспектов может дополнительно содержать один или несколько элементов включения и исключения (по умолчанию все определенные аспекты используются для объединения)
  • Weaver : определяет параметры ткача для ткача и указывает набор типов, которые должны быть сплетены. Если никакие включаемые элементы не указаны, то будут сплетены все типы, видимые ткачу.

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

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

Как мы видим, мы настроили аспект, который указывает на AccountAspect , и только исходный код в пакете com.foreach.aspectj будет вплетен AspectJ.

8. Аннотирование аспектов

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

Создадим аннотацию:

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

Мы используем аннотацию @Secured для включения или отключения метода:

public class SecuredMethod {

@Secured(isLocked = true)
public void lockedMethod() {
}

@Secured(isLocked = false)
public void unlockedMethod() {
}
}

Затем мы добавляем аспект, используя стиль аннотации AspectJ, и проверяем разрешение на основе атрибута аннотации @Secured:

@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 можно узнать по следующей ссылке .

Затем мы объединяем наш класс и аспект, используя Weaver во время загрузки, и помещаем aop.xml в папку META-INF :

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

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

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

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

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

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

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

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

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