Въведение в AspectJ

1. Въведение

Тази статия е кратко и практично въведение в AspectJ.

Първо ще покажем как да активираме ориентирано към аспекти програмиране и след това ще се съсредоточим върху разликата между тъкането по време на компилация, след компилация и зареждане.

Нека започнем с кратко въвеждане на аспектно-ориентирано програмиране (AOP) и основите на AspectJ.

2. Общ преглед

AOP е парадигма за програмиране, която има за цел да увеличи модулността, като позволява разделянето на междусекторните проблеми. Това се прави чрез добавяне на допълнително поведение към съществуващия код без промяна на самия код. Вместо това декларираме отделно кой код трябва да се модифицира.

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

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

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

В тази статия ние се фокусираме върху зависимостите, необходими за създаване на аспекти и Weaver, използвайки Weavers по време на компилация, след компилация и зареждане.

3.1. AspectJ Runtime

Когато се изпълнява програма AspectJ, пътят на класа трябва да съдържа класовете и аспектите заедно с библиотеката за изпълнение AspectJ аспектrt.jar :

 org.aspectj aspectjrt 1.8.9 

Тази зависимост е достъпна в Maven Central.

3.2. AspectJWeaver

Освен зависимостта на изпълнението на AspectJ, ще трябва да включим и aspektweaver.jar, за да въведем съвети за класа Java по време на зареждане:

 org.aspectj aspectjweaver 1.8.9 

Зависимостта е достъпна и на Maven Central.

4. Създаване на аспект

AspectJ осигурява изпълнение на AOP и има три основни концепции:

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

Ще демонстрираме тези концепции, като създадем проста програма за валидиране на баланса на потребителския акаунт.

Първо, нека създадем клас Акаунт с дадено салдо и метод за теглене:

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 : Редовен израз, който съвпада с точките на присъединяване. Съветът е свързан с израз на точка и се изпълнява във всяка точка на присъединяване, която съответства на точката

За повече подробности относно тези понятия и тяхната специфична семантика, може да поискаме да разгледаме следната връзка.

След това трябва да вплетем аспектите в нашия код. Разделите по-долу разглеждат три различни вида тъкане: тъкане по време на компилация, тъкане след компилиране и тъкане по време на натоварване в AspectJ.

5. Тъкане по време на компилация

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

Можем да изтеглим инструментите за разработка на AspectJ, тъй като той включва компилиран AspectJ компилатор. Една от най-важните характеристики на AJDT е инструмент за визуализация на кръстосани проблеми, който е полезен за отстраняване на грешки в спецификацията на точка. Можем да визуализираме комбинирания ефект дори преди кодът да бъде внедрен.

Използваме приставката AspectJ Maven на Mojo, за да вплетем аспекти на AspectJ в нашите класове, използвайки компилатора AspectJ.

 org.codehaus.mojo aspectj-maven-plugin 1.7  1.8 1.8 1.8 true true ignore UTF-8       compile  test-compile    

За повече подробности относно препратката към опциите на компилатора 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.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 файлове. Както при тъкането по време на компилация, аспектите, използвани за тъкане, могат да бъдат в източник или в двоична форма и самите те могат да бъдат изтъкани от аспекти.

За да направим това с приставката AspectJ Maven на Mojo, трябва да настроим всички JAR файлове, които бихме искали да вплетем в конфигурацията на приставката:

   org.agroup to-weave   org.anothergroup gen   

The JAR files containing the classes to weave must be listed as in the Maven project and listed as in the of the AspectJ Maven Plugin.

7. Load-Time Weaving

Load-time weaving is simply binary weaving deferred until the point that a class loader loads a class file and defines the class to the JVM.

To support this, one or more “weaving class loaders” are required. These are either provided explicitly by the run-time environment or enabled using a “weaving agent”.

7.1. Enabling Load-Time Weaving

AspectJ load-time weaving can be enabled using AspectJ agent that can get involved in the class loading process and weave any types before they are defined in the VM. We specify the javaagent option to the JVM -javaagent:pathto/aspectjweaver.jar or using Maven plugin to configure the javaagent :

 org.apache.maven.plugins maven-surefire-plugin 2.10   -javaagent:"${settings.localRepository}"/org/aspectj/ aspectjweaver/${aspectj.version}/ aspectjweaver-${aspectj.version}.jar  true always  

7.2. Configuration Weaver

AspectJ's load-time weaving agent is configured by the use of aop.xml files. It looks for one or more aop.xml files on the classpath in the META-INF directory and aggregates the contents to determine the weaver configuration.

An aop.xml file contains two key sections:

  • Aspects: defines one or more aspects to the weaver and controls which aspects are to be used in the weaving process. The aspects element may optionally contain one or more include and exclude elements (by default, all defined aspects are used for weaving)
  • Weaver: defines weaver options to the weaver and specifies the set of types that should be woven. If no include elements are specified then all types visible to the weaver will be woven

Let's configure an aspect to the weaver:

As we can see, we have configured an aspect that points to the AccountAspect, and only the source code in the com.baeldung.aspectj package will be woven by AspectJ.

8. Annotating Aspects

In addition to the familiar AspectJ code-based style of aspect declaration, AspectJ 5 also supports an annotation-based style of aspect declaration. We informally call the set of annotations that support this development style the “@AspectJ” annotations.

Let's create an annotation:

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

We use the @Secured annotation to enable or disable a method:

public class SecuredMethod { @Secured(isLocked = true) public void lockedMethod() { } @Secured(isLocked = false) public void unlockedMethod() { } }

Next, we add an aspect using AspectJ annotation-style, and check the permission based on the attribute of the @Secured annotation:

@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(); } }

For more detail on AspectJ annotation-style, we can check out the following link.

Next, we weave our class and aspect using load-time weaver and put aop.xml under META-INF folder:

Finally, we add unit test and check the result:

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

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

[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.

Можете да намерите изходния код за тази статия в GitHub.