CodeGym/Java блог/Случаен/Какво е AOP? Принципи на аспектно-ориентираното програмир...
John Squirrels
Ниво
San Francisco

Какво е AOP? Принципи на аспектно-ориентираното програмиране

Публикувано в групата
Здравейте, момчета и момичета! Без разбиране на основните концепции е доста трудно да се задълбочим в рамките и подходите за изграждане на функционалност. Така че днес ще говорим за една такава концепция — AOP, известно още като аспектно-ориентирано програмиране . Какво е AOP?  Принципи на аспектно-ориентираното програмиране - 1Тази тема не е лесна и рядко се използва директно, но много рамки и технологии я използват под капака. И разбира се, понякога по време на интервюта може да бъдете помолени да опишете в общи линии Howъв вид звяр е това и къде може да се приложи. Така че нека да разгледаме основните концепции и някои прости примери за AOP в Java . Сега, AOP означава аспектно-ориентирано програмиране, което е парадигма, предназначена да увеличи модулността на различните части на приложението чрез разделяне на междусекторни проблеми. За да се постигне това, към съществуващия code се добавя допълнително поведение, без да се правят промени в оригиналния code. С други думи, можем да мислим за това като закачане на допълнителна функционалност върху методите и класовете, без да променяме модифицирания code. Защо е необходимо това? Рано or късно заключаваме, че типичният обектно-ориентиран подход не винаги може ефективно да реши определени проблеми. И когато този момент настъпи, AOP идва на помощ и ни дава допълнителни инструменти за изграждане на applications. А допълнителните инструменти означават повишена гъвкавост при разработването на софтуер, което означава повече възможности за решаване на определен проблем.

Прилагане на AOP

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

  2. Целият изходен code за регистриране се съхранява на едно място, така че не е необходимо ръчно да търсите всички места, където се използва.

  3. Кодът за регистриране може да се добави навсякъде, независимо дали във вече написани методи и класове or в нова функционалност. Това намалява броя на грешките в codeирането.

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

  4. Аспектите са отделен code, който може да се подобрява и използва отново и отново.
Какво е AOP?  Принципи на аспектно-ориентираното програмиране - 2AOP също се използва за обработка на изключения, кеширане и извличане на определена функционалност, за да може да се използва повторно.

Основни принципи на АОП

За да продължим по-нататък в тази тема, нека първо се запознаем с основните концепции на AOP. Съвет — Допълнителна логика or code, извикан от точка на присъединяване. Съветите могат да бъдат изпълнени преди, след or instead of точка на присъединяване (повече за тях по-долу). Възможни видове съвети :
  1. Преди — този тип съвети се стартират преди да бъдат изпълнени целевите методи, т.е. точките за свързване. Когато използваме аспекти като класове, използваме анотацията @Before , за да маркираме съвета като идващ преди. Когато използвате аспекти като .aj файлове, това ще бъде методът before() .

  2. След — съвет, който се изпълнява след завършване на изпълнението на методи (точки на свързване), Howто при нормално изпълнение, така и при хвърляне на изключение.

    Когато използваме аспекти като класове, можем да използваме анотацията @After , за да посочим, че това е съвет, който идва след това.

    Когато използвате аспекти като .aj файлове, това е методът after() .

  3. След връщане — този съвет се изпълнява само когато целевият метод завърши нормално, без грешки.

    Когато аспектите са представени като класове, можем да използваме анотацията @AfterReturning , за да маркираме съвета като изпълняващ се след успешно завършване.

    Когато използвате аспекти като .aj файлове, това ще бъде методът after(), връщащ (Object obj) .

  4. След хвърляне — този съвет е предназначен за случаи, когато метод, тоест точка на присъединяване, хвърля изключение. Можем да използваме този съвет, за да се справим с определени видове неуспешно изпълнение (например за връщане назад на цяла транзакция or регистрационен файл с необходимото ниво на проследяване).

    За аспектите на класа се използва анотацията @AfterThrowing , за да се посочи, че този съвет се използва след хвърляне на изключение.

    Когато използвате аспекти като .aj файлове, това ще бъде методът after() за хвърляне (Изключение e) .

  5. Около — може би един от най-важните видове съвети. Той заобикаля метод, тоест точка на присъединяване, която можем да използваме, например, за да изберем дали да изпълним or не даден метод на точка на присъединяване.

    Можете да напишете code на съвет, който да се изпълнява преди и след изпълнението на метода на точката на присъединяване.

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

    При дадени аспекти като класове, ние използваме анотацията @Around , за да създадем съвет, който обвива точка на свързване. Когато използвате аспекти под формата на .aj файлове, този метод ще бъде методът around() .

Точка на присъединяване — точката в работеща програма (т.е. извикване на метод, създаване на обект, достъп до променлива), където трябва да се приложи съветът. С други думи, това е вид регулярен израз, използван за намиране на места за въвеждане на code (места, където трябва да се приложи съвет). Pointcut — набор от точки за свързване . Pointcut определя дали даденият съвет е приложим към дадена точка на свързване. Аспект — модул or клас, който реализира междусекторна функционалност. Aspect променя поведението на останалия code чрез прилагане на съвети в точките на свързване , определени от няHowъв pointcut . С други думи, това е комбинация от съвети и съединителни точки. Въведение— промяна на структурата на клас и/or промяна на йерархията на наследяване, за да се добави функционалността на аспекта към чужд code. Цел — обектът, към който ще се приложи съветът. Тъкане — процес на свързване на аспекти с други обекти за създаване на препоръчани прокси обекти. Това може да се направи по време на компorране, време на зареждане or време на изпълнение. Има три вида тъкане:
  • Тъкане по време на компorране — ако имате изходния code на аспекта и codeа, където използвате аспекта, тогава можете да компorрате изходния code и аспекта директно с помощта на AspectJ компилатора;

  • Преплитане след компorране (двоично преплитане) — ако не можете or не искате да използвате трансформации на изходния code за вплитане на аспекти в codeа, можете да вземете компorрани преди това класове or jar файлове и да инжектирате аспекти в тях;

  • Преплитане по време на зареждане — това е просто двоично преплитане, което се забавя, докато програмата за зареждане на класове зареди file на класа и дефинира класа за JVM.

    Необходими са един or повече зареждащи класове за тъкане, за да поддържат това. Те са or изрично предоставени от средата за изпълнение, or се активират от "агент за тъкане".

AspectJ — Специфично изпълнение на парадигмата на AOP , което прилага способността за изпълнение на междусекторни задачи. Документацията можете да намерите тук .

Примери в 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);
  }
}
Тук няма нищо сложно. Предадохме име и го показахме на конзолата. Ако стартираме програмата сега, ще видим следното на конзолата:
Танер Виктор Саша
Сега е време да се възползвате от силата на AOP. Сега трябва да създадем файл с аспекти . Те са два вида: първият има файлово разширение .aj . Вторият е обикновен клас, който използва анотации за реализиране на AOP възможности. Нека първо разгледаме file с разширение .aj :
public aspect GreetingAspect {

  pointcut greeting() : execution(* Main.printName(..));

  before() : greeting() {
     System.out.print("Hi, ");
  }
}
Този файл е нещо като клас. Нека да видим Howво се случва тук: pointcut е набор от точки за свързване; greeting() е името на този pointcut; : изпълнението показва да се приложи по време на изпълнението на всички ( * ) извиквания на метода Main.printName(...) . Следва конкретен съвет — before() — който се изпълнява преди извикването на целевия метод. : greeting() е точката на прекъсване, на която отговаря този съвет. Е, и по-долу виждаме тялото на самия метод, което е написано на езика Java, който разбираме. Когато стартираме main с този аспект, ще получим този конзолен изход:
Здравей, Танер, здравей, Виктор, здравей, Саша
Можем да видим, че всяко извикване на метода printName е променено благодарение на аспект. Сега нека да разгледаме How би изглеждал аспектът като Java клас с анотации:
@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() .
Изпълнението на main с този аспект не променя изхода на конзолата:
Здравей, Танер, здравей, Виктор, здравей, Саша

Пример №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 , получаваме това в конзолата:
Отваряне на транзакция... Извършване на някои операции за клиента Tanner Затваряне на транзакция...
Но ако хвърлим и изключение в нашия метод (за да симулираме неуспешна операция):
public static void performSomeOperation(String clientName) throws Exception {
  System.out.println("Performing some operations for Client " + clientName);
  throw new Exception();
}
Тогава получаваме този конзолен изход:
Отваряне на транзакция... Извършване на някои операции за клиента Tanner Операцията е неуспешна. Отмяна на транзакцията...
Така че това, което завършихме тук, е един вид способност за обработка на грешки.

Пример №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") е съвет, който ще бъде изпълнен след успешното изпълнение на целевия метод. Тук имаме два случая:
  1. Когато методът има върната стойност — if (returningValue! = Null) {
  2. Когато няма върната стойност — else {
@AfterThrowing(value = "methodExecuting()", throwing = "exception") е съвет, който ще бъде задействан в случай на грешка, тоест когато методът хвърля изключение. И съответно, като стартираме main , ще получим един вид конзолно базирано регистриране:
Успешно изпълнение: метод — setValue, клас — Основно Успешно изпълнение: метод — getValue, клас — Основно, върната стойност — <няHowва стойност> Изхвърлено изключение: метод — checkValue, клас — Основно изключение — java.lang.Exception Изхвърлено изключение: метод — main, клас — Main, изключение — java.lang.Exception
И тъй като не сме обработor изключенията, пак ще получим проследяване на стека: Какво е AOP?  Принципи на аспектно-ориентираното програмиране – 3Можете да прочетете за изключенията и обработката на изключения в тези статии: Изключения в Java и Изключения: прихващане и обработка . Това е всичко за мен днес. Днес се запознахме с AOP и вие успяхте да видите, че този звяр не е толкова страшен, колкото някои хора го представят. Довиждане на всички!
Коментари
  • Популярен
  • Нов
  • Стар
Трябва да сте влезли, за да оставите коментар
Тази страница все още няма коментари