JavaRush /Blog Java /Random-PL /Co to jest AOP? Podstawy programowania aspektowego

Co to jest AOP? Podstawy programowania aspektowego

Opublikowano w grupie Random-PL
Cześć chłopaki! Bez zrozumienia podstawowych pojęć dość trudno jest zagłębić się w ramy i podejścia do budowania funkcjonalności. Dlatego dzisiaj porozmawiamy o jednym z tych pojęć – AOP, czyli programowaniu aspektowym . Co to jest AOP?  Podstawy programowania aspektowego - 1Nie jest to łatwy temat i nie jest często używany bezpośrednio, ale wiele frameworków i technologii wykorzystuje go pod maską. Oczywiście czasami podczas rozmów kwalifikacyjnych możesz zostać poproszony o ogólne opowiedzenie, jakiego rodzaju jest to zwierzę i gdzie można je wykorzystać. Przyjrzyjmy się więc podstawowym pojęciom i kilku prostym przykładom AOP w Javie . Co to jest AOP?  Podstawy programowania aspektowego - 2Zatem AOPprogramowanie aspektowe – jest paradygmatem mającym na celu zwiększenie modułowości różnych części aplikacji poprzez oddzielenie zagadnień przekrojowych. Aby to zrobić, do istniejącego kodu dodaje się dodatkowe zachowanie, bez zmiany oryginalnego kodu. Innymi słowy, wydaje się, że dodajemy dodatkową funkcjonalność do metod i klas, nie wprowadzając poprawek do zmodyfikowanego kodu. Dlaczego jest to konieczne? Prędzej czy później dochodzimy do wniosku, że zwykłe podejście obiektowe nie zawsze może skutecznie rozwiązać pewne problemy. W takim momencie z pomocą przychodzi AOP , który udostępnia nam dodatkowe narzędzia do budowy aplikacji. A dodatkowe narzędzia oznaczają większą elastyczność w rozwoju, dzięki czemu istnieje więcej opcji rozwiązania konkretnego problemu.

Zastosowanie AOP

Programowanie aspektowe ma na celu rozwiązywanie problemów przekrojowych, którymi może być dowolny kod wielokrotnie powtarzany na różne sposoby, którego nie można w całości ułożyć w osobny moduł. W związku z tym w AOP możemy pozostawić to poza głównym kodem i zdefiniować go pionowo. Przykładem jest zastosowanie polityki bezpieczeństwa w aplikacji. Zwykle bezpieczeństwo obejmuje wiele elementów aplikacji. Co więcej, polityka bezpieczeństwa aplikacji musi być stosowana jednakowo do wszystkich istniejących i nowych części aplikacji. Jednocześnie stosowana polityka bezpieczeństwa może sama ewoluować. Tutaj z pomocą może przyjść użycie AOP . Kolejnym przykładem jest logowanie . Stosowanie podejścia AOP do rejestrowania ma kilka zalet w porównaniu z ręcznym wstawianiem rejestrowania:
  1. Kod rejestrujący jest łatwy do wdrożenia i usunięcia: wystarczy dodać lub usunąć kilka konfiguracji jakiegoś aspektu.
  2. Cały kod źródłowy do logowania jest przechowywany w jednym miejscu i nie ma potrzeby ręcznego wyszukiwania wszystkich miejsc użycia.
  3. Kod przeznaczony do logowania można dodać w dowolnym miejscu, niezależnie od tego, czy są to już napisane metody i klasy, czy też nowa funkcjonalność. Zmniejsza to liczbę błędów programistów.
    Ponadto, gdy usuniesz aspekt z konfiguracji projektu, możesz być absolutnie pewien, że cały kod śledzenia został usunięty i niczego nie brakuje.
  4. Aspekty to samodzielny kod, który można wielokrotnie wykorzystywać i ulepszać.
Co to jest AOP?  Podstawy programowania aspektowego - 3AOP jest również używany do obsługi wyjątków, buforowania i usuwania niektórych funkcji, aby umożliwić ich ponowne użycie.

Podstawowe pojęcia AOP

Aby przejść dalej w analizie tematu, zapoznajmy się najpierw z głównymi koncepcjami AOP. Porada to dodatkowa logika, kod wywoływany z punktu połączenia. Poradę można wykonać przed, za lub zamiast miejsca podłączenia (więcej o nich poniżej). Możliwe rodzaje porad :
  1. Before (Before) - porada tego typu uruchamiana jest przed wykonaniem metod docelowych - punktów połączeń. Używając aspektów jako klas, używamy adnotacji @Before , aby oznaczyć typ porady jako występujący wcześniej. W przypadku używania aspektów jako plików .aj będzie to metoda before() .
  2. After (After) - porada wykonywana po zakończeniu wykonywania metod - punktów połączenia, zarówno w normalnych przypadkach, jak i wtedy, gdy zostanie zgłoszony wyjątek.
    Używając aspektów jako klas, możemy użyć adnotacji @After , aby wskazać, że jest to wskazówka, która pojawia się później.
    W przypadku używania aspektów jako plików .aj będzie to metoda after() .
  3. After Return - te wskazówki są wykonywane tylko wtedy, gdy docelowa metoda działa normalnie, bez błędów.
    Kiedy aspekty są reprezentowane jako klasy, możemy użyć adnotacji @AfterReturning , aby oznaczyć poradę jako wykonaną po pomyślnym zakończeniu.
    Jeśli używasz aspektów jako plików .aj, będzie to metoda after() zwracająca (Object obj) .
  4. After Throwing – tego typu porady są przeznaczone w przypadkach, gdy metoda, czyli punkt połączenia, zgłasza wyjątek. Możemy skorzystać z tej porady w przypadku nieudanej realizacji (na przykład wycofania całej transakcji lub logowania z wymaganym poziomem śledzenia).
    W przypadku klas aspektów używana jest adnotacja @AfterThrowing , aby wskazać, że ta rada zostanie użyta po zgłoszeniu wyjątku.
    W przypadku stosowania aspektów w postaci plików .aj będzie to metoda - after() rzucająca (Wyjątek e) .
  5. Around to chyba jeden z najważniejszych rodzajów porad otaczających metodę, czyli punkt połączenia, za pomocą którego możemy na przykład wybrać, czy wykonać daną metodę punktu połączenia, czy nie.
    Możesz napisać kod porad, który będzie uruchamiany przed i po wykonaniu metody punktu łączenia.
    Do obowiązków porady należy wywoływanie metody punktu łączenia i zwracanie wartości, jeśli metoda coś zwróci. Oznacza to, że w tej wskazówce możesz po prostu naśladować działanie metody docelowej bez jej wywoływania i w rezultacie zwrócić coś własnego.
    W przypadku aspektów w postaci klas używamy adnotacji @Around , aby utworzyć wskazówki otaczające punkt połączenia. W przypadku używania aspektów jako plików .aj będzie to metoda Around() .
Punkt łączenia - punkt w wykonywającym się programie (wywołanie metody, utworzenie obiektu, dostęp do zmiennej), w którym należy zastosować poradę. Innymi słowy, jest to pewnego rodzaju wyrażenie regularne, za pomocą którego wyszukuje się miejsca na wprowadzenie kodu (miejsca zastosowania wskazówek). Punkt cięty to zbiór punktów połączenia . Od wycięcia zależy, czy dany punkt połączenia pasuje do danej końcówki. Aspekt to moduł lub klasa implementująca kompleksową funkcjonalność. Aspekt modyfikuje zachowanie reszty kodu, stosując porady w punktach łączenia zdefiniowanych przez jakiś wycinek . Innymi słowy jest to połączenie końcówek i punktów połączeń. Wprowadzenie - zmiana struktury klasy i/lub zmiana hierarchii dziedziczenia w celu dodania funkcjonalności aspektowej do obcego kodu. Cel to obiekt, do którego zostanie zastosowana porada. Splot to proces łączenia aspektów z innymi obiektami w celu utworzenia zalecanych obiektów zastępczych. Można to zrobić w czasie kompilacji, ładowania lub wykonywania. Istnieją trzy rodzaje tkania:
  • Tkanie w czasie kompilacji — jeśli masz kod źródłowy aspektu i kod, w którym używasz aspektów, możesz skompilować kod źródłowy i aspekt bezpośrednio przy użyciu kompilatora AspectJ;
  • tkanie pokompilacyjne (tkanie binarne) - jeśli nie możesz lub nie chcesz używać transformacji kodu źródłowego do wplatania aspektów w swój kod, możesz skorzystać z już skompilowanych klas lub słoików i wstrzyknąć aspekty;
  • tkanie w czasie ładowania to po prostu tkanie binarne odroczone do czasu, aż moduł ładujący klasy załaduje plik klasy i zdefiniuje klasę dla maszyny JVM.
    Aby to obsłużyć, wymagany jest jeden lub więcej „ładowaczy klas splotu”. Są one albo jawnie dostarczane przez środowisko wykonawcze, albo aktywowane przez „agenta tkającego”.
AspectJ to specyficzna implementacja paradygmatów AOP , która implementuje możliwość rozwiązywania problemów przekrojowych. Dokumentację można znaleźć tutaj .

Przykłady w Javie

Następnie, aby lepiej zrozumieć AOP, przyjrzymy się małym przykładom poziomu Hello World. Co to jest AOP?  Podstawy programowania aspektowego - 4Od razu zauważę, że w naszych przykładach będziemy używać tkania w czasie kompilacji . Najpierw musimy dodać następującą zależność do naszego pom.xml :
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
Z reguły do ​​korzystania z aspektów używany jest specjalny kompilator Ajs . IntelliJ IDEA nie ma go domyślnie, więc wybierając go jako kompilator aplikacji należy podać ścieżkę do dystrybucji AspectJ . Więcej o sposobie wyboru Ajsa jako kompilatora możesz przeczytać na tej stronie. To była pierwsza metoda, a drugą (którą zastosowałem) było dodanie następującej wtyczki do 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>
Następnie zaleca się ponowne zaimportowanie z Mavena i uruchomienie mvn clean Compare . Przejdźmy teraz do przykładów.

Przykład nr 1

Stwórzmy klasę Main . Będziemy w nim mieli punkt uruchamiania i metodę, która wypisuje w konsoli przekazane mu nazwy:
public class Main {

  public static void main(String[] args) {
  printName("Толя");
  printName("Вова");
  printName(„Sasza”);
  }

  public static void printName(String name) {
     System.out.println(name);
  }
}
Nic skomplikowanego: przekazali nazwę i wyświetlili ją w konsoli. Jeśli uruchomimy to teraz, konsola wyświetli:
Tolia Wowa Sasza
No cóż, czas skorzystać z potęgi AOP. Teraz musimy utworzyć aspekt pliku . Występują w dwóch typach: pierwszy to plik z rozszerzeniem .aj , drugi to zwykła klasa, która implementuje możliwości AOP za pomocą adnotacji. Przyjrzyjmy się najpierw plikowi z rozszerzeniem .aj :
public aspect GreetingAspect {

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

  before() : greeting() {
     System.out.print("Привет ");
  }
}
Ten plik jest nieco podobny do klasy. Zastanówmy się, co się tutaj dzieje: pointcut - cięcie lub zbiór punktów połączenia; powitanie() — nazwa tego plasterka; : wykonanie - podczas wykonywania * - all, wywołaj - Main.printName(..) - tę metodę. Następnie następuje konkretna rada - before() - która jest wykonywana przed wywołaniem metody docelowej, : powitanie() - wycinek, na który reaguje ta rada, a poniżej widzimy treść samej metody napisaną w Javie język, który rozumiemy. Kiedy uruchomimy main z obecnym tym aspektem, otrzymamy następujące dane wyjściowe na konsolę:
Witaj Tolya. Witaj Vova. Witaj Sasza
Widzimy, że każde wywołanie metody printName zostało zmodyfikowane przez aspekt. Przyjrzyjmy się teraz, jak będzie wyglądał ten aspekt, ale jako klasa Java z adnotacjami:
@Aspect
public class GreetingAspect{

  @Pointcut("execution(* Main.printName(String))")
  public void greeting() {
  }

  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Привет ");
  }
}
Po pliku aspektu .aj wszystko jest bardziej oczywiste:
  • @Aspect oznacza, że ​​dana klasa jest aspektem;
  • @Pointcut("execution(* Main.printName(String))") to punkt odcięcia uruchamiany przy wszystkich wywołaniach Main.printName z przychodzącym argumentem typu String ;
  • @Before("greeting()") - rada stosowana przed wywołaniem kodu opisanego w punkcie odcięcia powitania() .
Uruchomienie main z tym aspektem nie zmieni danych wyjściowych konsoli:
Witaj Tolya. Witaj Vova. Witaj Sasza

Przykład nr 2

Załóżmy, że mamy metodę, która wykonuje pewne operacje dla klientów i wywołujemy tę metodę z poziomu głównego :
public class Main {

  public static void main(String[] args) {
  makeSomeOperation("Толя");
  }

  public static void makeSomeOperation(String clientName) {
     System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  }
}
Używając adnotacji @Around , zróbmy coś w rodzaju „pseudotransakcji”:
@Aspect
public class TransactionAspect{

  @Pointcut("execution(* Main.makeSomeOperation(String))")
  public void executeOperation() {
  }

  @Around(value = "executeOperation()")
  public void beforeAdvice(ProceedingJoinPoint joinPoint) {
     System.out.println("Открытие транзакции...");
     try {
        joinPoint.proceed();
        System.out.println("Закрытие транзакции....");
     }
     catch (Throwable throwable) {
        System.out.println("Операция не удалась, откат транзакции...");
     }
  }
  }
Wykorzystując metodę postępowania obiektu ProceedingJoinPoint wywołujemy metodę wrappera w celu określenia jego miejsca na planszy i odpowiednio kodu w powyższej metodzie JoinPoint.proceed(); - to jest Przed , które jest poniżej - Po . Jeżeli uruchomimy main to w konsoli otrzymamy:
Otwarcie transakcji... Wykonanie niektórych operacji dla klienta - Tolya Zamykanie transakcji....
Jeśli do naszej metody dodamy rzut wyjątku (nagle operacja się nie powiedzie):
public static void makeSomeOperation(String clientName)throws Exception {
  System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  throw new Exception();
}
Następnie otrzymamy wynik w konsoli:
Otwieranie transakcji... Wykonywanie niektórych operacji dla klienta - Tolya Operacja nie powiodła się, transakcja została wycofana...
Okazało się, że było to pseudoprzetwarzanie awarii.

Przykład nr 3

W następnym przykładzie zróbmy coś takiego jak logowanie do konsoli. Najpierw spójrzmy na Main , gdzie dzieje się nasza pseudo logika biznesowa:
public class Main {
  private String value;

  public static void main(String[] args) throws Exception {
     Main main = new Main();
     main.setValue("<некоторое oznaczający>");
     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();
     }
  }
}
W main za pomocą setValue ustawimy wartość zmiennej wewnętrznej - wartość , następnie za pomocą getValue przyjmiemy tę wartość i w checkValue sprawdzimy, czy wartość ta jest dłuższa niż 10 znaków. Jeśli tak, zostanie zgłoszony wyjątek. Przyjrzyjmy się teraz aspektowi, z jakim będziemy rejestrować działanie metod:
@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("Успешно выполнен метод - %s, класса- %s, с результатом выполнения - %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName(),
              returningValue);
     }
     else {
        System.out.printf("Успешно выполнен метод - %s, класса- %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName());
     }
  }

  @AfterThrowing(value = "methodExecuting()", throwing = "exception")
  public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
     System.out.printf("Метод - %s, класса- %s, был аварийно завершен с исключением - %s\n",
           joinPoint.getSignature().getName(),
           joinPoint.getSourceLocation().getWithinType().getName(),
           exception);
  }
}
Co tu się dzieje? @Pointcut("execution(* *(..))") - połączy się ze wszystkimi wywołaniami wszystkich metod; @AfterReturning(value = "methodExecuting()", return = "returningValue") - porada, która zostanie wykonana po pomyślnym zakończeniu docelowej metody. Mamy tu dwa przypadki:
  1. Gdy metoda zwraca wartość if (returningValue != null) {
  2. Gdy nie ma wartości zwracanej else {
@AfterThrowing(value = "methodExecuting()", rzucanie = "wyjątek") - rada, która zostanie wywołana w przypadku błędu, czyli zgłoszenia wyjątku od metody. I odpowiednio, uruchamiając main , otrzymamy coś w rodzaju logowania w konsoli:
Metoda - setValue klasy - Main została wykonana pomyślnie Metoda - getValue klasy - Main została wykonana pomyślnie, z wynikiem wykonania - <jakaś wartość> Metoda - checkValue klasy - Main, zostało zakończone nieprawidłowo z powodu wyjątku - java.lang.Exception Metoda - main, class-Main, uległa awarii z powodu wyjątku - java.lang.Exception
Cóż, ponieważ nie obsłużyliśmy wyjątku, otrzymamy również jego stos śledzenia: Co to jest AOP?  Podstawy programowania aspektowego - 5O wyjątkach i ich obsłudze możesz przeczytać w tych artykułach: Wyjątki w Javie i Wyjątki i ich obsługa . To wszystko dla mnie dzisiaj. Dziś zapoznaliśmy się z AOP i widać było, że ta bestia nie jest taka straszna jak ją przedstawiają. Do widzenia wszystkim!Co to jest AOP?  Podstawy programowania aspektowego - 6
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION