
Прилагане на AOP
Аспектно-ориентираното програмиране е проектирано да изпълнява междусекторни задачи, които могат да бъдат всеки code, който може да се повтаря много пъти чрез различни методи, който не може да бъде напълно структуриран в отделен модул. Съответно AOP ни позволява да запазим това извън основния code и да го декларираме вертикално. Пример е използването на политика за сигурност в приложение. Обикновено сигурността преминава през много елементи на приложението. Нещо повече, политиката за сигурност на приложението трябва да се прилага еднакво към всички съществуващи и нови части на приложението. В същото време използваната политика за сигурност може сама да се развива. Това е идеалното място за използване на AOP . Също така, друг пример е регистриране. Има няколко предимства при използването на AOP подход за регистриране, instead of ръчно добавяне на функция за регистриране:-
Кодът за регистриране е лесен за добавяне и премахване: всичко, което трябва да направите, е да добавите or премахнете няколко конфигурации на няHowъв аспект.
-
Целият изходен code за регистриране се съхранява на едно място, така че не е необходимо ръчно да търсите всички места, където се използва.
-
Кодът за регистриране може да се добави навсякъде, независимо дали във вече написани методи и класове or в нова функционалност. Това намалява броя на грешките в codeирането.
Освен това, когато премахвате аспект от конфигурация на дизайн, можете да сте сигурни, че целият code за проследяване е изчезнал и че нищо не е пропуснато.
- Аспектите са отделен code, който може да се подобрява и използва отново и отново.

Основни принципи на АОП
За да продължим по-нататък в тази тема, нека първо се запознаем с основните концепции на AOP. Съвет — Допълнителна логика or code, извикан от точка на присъединяване. Съветите могат да бъдат изпълнени преди, след or instead of точка на присъединяване (повече за тях по-долу). Възможни видове съвети :-
Преди — този тип съвети се стартират преди да бъдат изпълнени целевите методи, т.е. точките за свързване. Когато използваме аспекти като класове, използваме анотацията @Before , за да маркираме съвета като идващ преди. Когато използвате аспекти като .aj файлове, това ще бъде методът before() .
- След — съвет, който се изпълнява след завършване на изпълнението на методи (точки на свързване), Howто при нормално изпълнение, така и при хвърляне на изключение.
Когато използваме аспекти като класове, можем да използваме анотацията @After , за да посочим, че това е съвет, който идва след това.
Когато използвате аспекти като .aj файлове, това е методът after() .
-
След връщане — този съвет се изпълнява само когато целевият метод завърши нормално, без грешки.
Когато аспектите са представени като класове, можем да използваме анотацията @AfterReturning , за да маркираме съвета като изпълняващ се след успешно завършване.
Когато използвате аспекти като .aj файлове, това ще бъде методът after(), връщащ (Object obj) .
-
След хвърляне — този съвет е предназначен за случаи, когато метод, тоест точка на присъединяване, хвърля изключение. Можем да използваме този съвет, за да се справим с определени видове неуспешно изпълнение (например за връщане назад на цяла транзакция or регистрационен файл с необходимото ниво на проследяване).
За аспектите на класа се използва анотацията @AfterThrowing , за да се посочи, че този съвет се използва след хвърляне на изключение.
Когато използвате аспекти като .aj файлове, това ще бъде методът after() за хвърляне (Изключение e) .
-
Около — може би един от най-важните видове съвети. Той заобикаля метод, тоест точка на присъединяване, която можем да използваме, например, за да изберем дали да изпълним or не даден метод на точка на присъединяване.
Можете да напишете code на съвет, който да се изпълнява преди и след изпълнението на метода на точката на присъединяване.
Съветът около е отговорен за извикването на метода на точката на присъединяване и връщаните стойности, ако методът върне нещо. С други думи, в този съвет можете просто да симулирате работата на целеви метод, без да го извиквате, и да върнете Howвото искате като върнат резултат.
При дадени аспекти като класове, ние използваме анотацията @Around , за да създадем съвет, който обвива точка на свързване. Когато използвате аспекти под формата на .aj файлове, този метод ще бъде методът around() .
-
Тъкане по време на компorране — ако имате изходния code на аспекта и codeа, където използвате аспекта, тогава можете да компorрате изходния code и аспекта директно с помощта на AspectJ компилатора;
-
Преплитане след компorране (двоично преплитане) — ако не можете or не искате да използвате трансформации на изходния code за вплитане на аспекти в codeа, можете да вземете компorрани преди това класове or jar файлове и да инжектирате аспекти в тях;
-
Преплитане по време на зареждане — това е просто двоично преплитане, което се забавя, докато програмата за зареждане на класове зареди file на класа и дефинира класа за JVM.
Необходими са един or повече зареждащи класове за тъкане, за да поддържат това. Те са or изрично предоставени от средата за изпълнение, or се активират от "агент за тъкане".
Примери в Java
След това, за по-добро разбиране на AOP , ще разгледаме малки примери в стил "Hello World". Отдясно ще отбележа, че нашите примери ще използват сплитане по време на компилация . Първо, трябва да добавим следната зависимост в нашия файл pom.xml :
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
Като правило, специалният ajc компилатор е начинът, по който използваме аспектите. IntelliJ IDEA не го включва по подразбиране, така че когато го избирате като компилатор на приложение, трябва да посочите пътя до 5168 75 AspectJ разпространение. Това беше първият начин. Вторият, който използвах, е да регистрирам следния плъгин във file pom.xml :
<build>
<plugins>
<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>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
След това е добра идея да импортирате отново от Maven и да стартирате mvn clean compile . Сега нека да продължим директно към примерите.
Пример №1
Нека създадем главен клас. В него ще имаме входна точка и метод, който отпечатва предадено име на конзолата:
public class Main {
public static void main(String[] args) {
printName("Tanner");
printName("Victor");
printName("Sasha");
}
public static void printName(String name) {
System.out.println(name);
}
}
Тук няма нищо сложно. Предадохме име и го показахме на конзолата. Ако стартираме програмата сега, ще видим следното на конзолата:
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Hi, ");
}
}
Този файл е нещо като клас. Нека да видим Howво се случва тук: pointcut е набор от точки за свързване; greeting() е името на този pointcut; : изпълнението показва да се приложи по време на изпълнението на всички ( * ) извиквания на метода Main.printName(...) . Следва конкретен съвет — before() — който се изпълнява преди извикването на целевия метод. : greeting() е точката на прекъсване, на която отговаря този съвет. Е, и по-долу виждаме тялото на самия метод, което е написано на езика Java, който разбираме. Когато стартираме main с този аспект, ще получим този конзолен изход:
@Aspect
public class GreetingAspect{
@Pointcut("execution(* Main.printName(String))")
public void greeting() {
}
@Before("greeting()")
public void beforeAdvice() {
System.out.print("Hi, ");
}
}
След .aj аспектния файл всичко става по-очевидно тук:
- @Aspect показва, че този клас е аспект;
- @Pointcut("execution(* Main.printName(String))") е точката на прекъсване, която се задейства за всички извиквания на Main.printName с входен аргумент, чийто тип е String ;
- @Before("greeting()") е съвет, който се прилага преди извикване на codeа, посочен в точката на прекъсване greeting() .
Пример №2
Да предположим, че имаме няHowъв метод, който изпълнява някои операции за клиенти, и извикваме този метод от main :
public class Main {
public static void main(String[] args) {
performSomeOperation("Tanner");
}
public static void performSomeOperation(String clientName) {
System.out.println("Performing some operations for Client " + clientName);
}
}
Нека използваме анотацията @Around , за да създадем „псевдо-транзакция“:
@Aspect
public class TransactionAspect{
@Pointcut("execution(* Main.performSomeOperation(String))")
public void executeOperation() {
}
@Around(value = "executeOperation()")
public void beforeAdvice(ProceedingJoinPoint joinPoint) {
System.out.println("Opening a transaction...");
try {
joinPoint.proceed();
System.out.println("Closing a transaction...");
}
catch (Throwable throwable) {
System.out.println("The operation failed. Rolling back the transaction...");
}
}
}
С метода continue на обекта ProceedingJoinPoint ние извикваме метода за обвиване, за да определим местоположението му в съвета. Следователно codeът в метода по-горе joinPoint.proceed(); е Преди , докато codeът под него е След . Ако стартираме main , получаваме това в конзолата:
public static void performSomeOperation(String clientName) throws Exception {
System.out.println("Performing some operations for Client " + clientName);
throw new Exception();
}
Тогава получаваме този конзолен изход:
Пример №3
В следващия ни пример нека направим нещо като логване в конзолата. Първо, погледнете Main , където сме добавor малко псевдобизнес логика:
public class Main {
private String value;
public static void main(String[] args) throws Exception {
Main main = new Main();
main.setValue("<some value>");
String valueForCheck = main.getValue();
main.checkValue(valueForCheck);
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
public void checkValue(String value) throws Exception {
if (value.length() > 10) {
throw new Exception();
}
}
}
В main използваме setValue , за да присвоим стойност на променливата на екземпляра на стойността . След това използваме getValue , за да получим стойността, и след това извикваме checkValue, за да видим дали е по-дълъг от 10 знака. Ако е така, тогава ще бъде хвърлено изключение. Сега нека разгледаме аспекта, който ще използваме, за да регистрираме работата на методите:
@Aspect
public class LogAspect {
@Pointcut("execution(* *(..))")
public void methodExecuting() {
}
@AfterReturning(value = "methodExecuting()", returning = "returningValue")
public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) {
if (returningValue != null) {
System.out.printf("Successful execution: method — %s method, class — %s class, return value — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
returningValue);
}
else {
System.out.printf("Successful execution: method — %s, class — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName());
}
}
@AfterThrowing(value = "methodExecuting()", throwing = "exception")
public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
System.out.printf("Exception thrown: method — %s, class — %s, exception — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
exception);
}
}
Какво става тук? @Pointcut("изпълнение(* *(..))") ще се присъедини към всички извиквания на всички методи. @AfterReturning(value = "methodExecuting()", returning = "returningValue") е съвет, който ще бъде изпълнен след успешното изпълнение на целевия метод. Тук имаме два случая:
- Когато методът има върната стойност — if (returningValue! = Null) {
- Когато няма върната стойност — else {

GO TO FULL VERSION