JavaRush /Java-Blog /Random-DE /Was ist AOP? Grundlagen der aspektorientierten Programmie...

Was ist AOP? Grundlagen der aspektorientierten Programmierung

Veröffentlicht in der Gruppe Random-DE
Hallo Leute! Ohne Verständnis der Grundkonzepte ist es ziemlich schwierig, sich mit Frameworks und Ansätzen zum Aufbau von Funktionalität zu befassen. Deshalb werden wir heute über eines dieser Konzepte sprechen – AOP oder aspektorientierte Programmierung . Was ist AOP?  Grundlagen der aspektorientierten Programmierung - 1Dies ist kein einfaches Thema und wird nicht oft direkt verwendet, aber viele Frameworks und Technologien nutzen es unter der Haube. Und natürlich werden Sie bei Vorstellungsgesprächen manchmal gebeten, Ihnen allgemein zu sagen, um welche Art von Tier es sich handelt und wo es verwendet werden kann. Schauen wir uns also die Grundkonzepte und einige einfache Beispiele von AOP in Java an . AOPaspektorientierte ProgrammierungWas ist AOP?  Grundlagen der aspektorientierten Programmierung - 2 – ist also ein Paradigma, das darauf abzielt, die Modularität verschiedener Teile einer Anwendung durch die Trennung übergreifender Belange zu erhöhen. Dazu wird dem vorhandenen Code zusätzliches Verhalten hinzugefügt, ohne den ursprünglichen Code zu ändern. Mit anderen Worten: Es scheint, dass wir den Methoden und Klassen zusätzliche Funktionalität hinzufügen, ohne Änderungen am geänderten Code vorzunehmen. Warum ist das notwendig? Früher oder später kommen wir zu dem Schluss, dass der übliche objektorientierte Ansatz bestimmte Probleme nicht immer effektiv lösen kann. In einem solchen Moment kommt AOP zur Rettung und stellt uns zusätzliche Tools zum Erstellen der Anwendung zur Verfügung. Und zusätzliche Tools bedeuten eine größere Flexibilität in der Entwicklung, wodurch mehr Möglichkeiten zur Lösung eines bestimmten Problems bestehen.

Anwendung von AOP

Aspektorientierte Programmierung soll übergreifende Probleme lösen, bei denen es sich um Code handeln kann, der viele Male auf unterschiedliche Weise wiederholt wird und nicht vollständig in ein separates Modul strukturiert werden kann. Dementsprechend können wir dies mit AOP außerhalb des Hauptcodes belassen und vertikal definieren. Ein Beispiel ist die Anwendung einer Sicherheitsrichtlinie in einer Anwendung. Typischerweise betrifft die Sicherheit viele Elemente einer Anwendung. Darüber hinaus muss die Anwendungssicherheitsrichtlinie gleichermaßen auf alle bestehenden und neuen Teile der Anwendung angewendet werden. Gleichzeitig kann sich die verwendete Sicherheitsrichtlinie weiterentwickeln. Hier kann der Einsatz von AOP hilfreich sein . Ein weiteres Beispiel ist die Protokollierung . Die Verwendung eines AOP- Ansatzes für die Protokollierung bietet im Vergleich zum manuellen Einfügen der Protokollierung mehrere Vorteile:
  1. Protokollierungscode ist einfach zu implementieren und zu entfernen: Sie müssen lediglich einige Konfigurationen eines bestimmten Aspekts hinzufügen oder entfernen.
  2. Der gesamte Quellcode für die Protokollierung wird an einem Ort gespeichert und es ist nicht erforderlich, alle Verwendungsorte manuell zu finden.
  3. Für die Protokollierung vorgesehener Code kann überall hinzugefügt werden, seien es bereits geschriebene Methoden und Klassen oder neue Funktionen. Dies reduziert die Anzahl der Entwicklerfehler.
    Wenn Sie einen Aspekt aus einer Designkonfiguration entfernen, können Sie außerdem absolut sicher sein, dass der gesamte Trace-Code entfernt wird und nichts fehlt.
  4. Aspekte sind eigenständiger Code, der immer wieder verwendet und verbessert werden kann.
Was ist AOP?  Grundlagen der aspektorientierten Programmierung - 3AOP wird auch zur Ausnahmebehandlung, zum Caching und zum Entfernen einiger Funktionen verwendet, um es wiederverwendbar zu machen.

Grundkonzepte von AOP

Um die Analyse des Themas weiter voranzutreiben, machen wir uns zunächst mit den Hauptkonzepten von AOP vertraut. Advice ist zusätzliche Logik, Code, der vom Verbindungspunkt aufgerufen wird. Die Beratung kann vor, nach oder anstelle des Verbindungspunkts durchgeführt werden (mehr dazu weiter unten). Mögliche Beratungsarten :
  1. Vorher (Vorher) – Hinweise dieser Art werden vor der Ausführung von Zielmethoden – Verbindungspunkten – gestartet. Wenn Aspekte als Klassen verwendet werden, verwenden wir die Annotation @Before , um den Beratungstyp als vorher kommend zu markieren. Wenn Aspekte als .aj- Dateien verwendet werden , ist dies die Methode before() .
  2. After (After) – Hinweis, der nach Abschluss der Ausführung von Methoden ausgeführt wird – Verbindungspunkte, sowohl im Normalfall als auch beim Auslösen einer Ausnahme.
    Wenn wir Aspekte als Klassen verwenden, können wir die Annotation @After verwenden, um anzugeben, dass es sich um einen Tipp handelt, der danach folgt.
    Wenn Aspekte als .aj- Dateien verwendet werden , ist dies die Methode after() .
  3. Nach der Rückkehr – diese Tipps werden nur ausgeführt, wenn die Zielmethode normal und ohne Fehler funktioniert.
    Wenn Aspekte als Klassen dargestellt werden, können wir die Annotation @AfterReturning verwenden , um den Hinweis nach erfolgreichem Abschluss als ausgeführt zu markieren.
    Wenn Aspekte als .aj-Dateien verwendet werden, ist dies die Methode after() , die (Object obj) zurückgibt .
  4. Nach dem Auslösen – diese Art von Ratschlägen ist für die Fälle gedacht, in denen eine Methode, also ein Verbindungspunkt, eine Ausnahme auslöst. Wir können diesen Rat für die Behandlung der fehlgeschlagenen Ausführung verwenden (z. B. ein Rollback der gesamten Transaktion oder eine Protokollierung mit der erforderlichen Trace-Ebene).
    Bei Aspektklassen wird die Annotation @AfterThrowing verwendet, um anzugeben, dass dieser Hinweis verwendet wird, nachdem eine Ausnahme ausgelöst wurde.
    Bei Verwendung von Aspekten in Form von .aj- Dateien ist dies die Methode - after() throwing (Exception e) .
  5. Around ist vielleicht eine der wichtigsten Arten von Ratschlägen rund um eine Methode, also einen Verbindungspunkt, mit dem wir beispielsweise entscheiden können, ob eine bestimmte Verbindungspunktmethode ausgeführt werden soll oder nicht.
    Sie können Empfehlungscode schreiben, der vor und nach der Ausführung der Join-Point-Methode ausgeführt wird.
    Zu den Aufgaben von Around Advice gehört das Aufrufen der Join-Point-Methode und das Zurückgeben von Werten, wenn die Methode etwas zurückgibt. Das heißt, in diesem Tipp können Sie einfach die Funktionsweise der Zielmethode nachahmen, ohne sie aufzurufen, und als Ergebnis etwas Eigenes zurückgeben.
    Für Aspekte in Form von Klassen verwenden wir die Annotation @Around, um Tipps zu erstellen, die den Verbindungspunkt umschließen. Wenn Aspekte als .aj- Dateien verwendet werden , ist dies die Methode around() .
Verbindungspunkt – ein Punkt in einem ausführenden Programm (Aufruf einer Methode, Erstellen eines Objekts, Zugriff auf eine Variable), an dem Ratschläge angewendet werden sollen. Mit anderen Worten handelt es sich hierbei um eine Art regulären Ausdruck, mit dessen Hilfe Stellen zum Einführen von Code (Stellen zum Anwenden von Tipps) gefunden werden. Ein Pointcut ist eine Menge von Verbindungspunkten . Der Schnitt bestimmt, ob ein bestimmter Verbindungspunkt zu einer bestimmten Spitze passt. Aspect ist ein Modul oder eine Klasse, die End-to-End-Funktionalität implementiert. Ein Aspekt ändert das Verhalten des restlichen Codes, indem er Ratschläge an Verbindungspunkten anwendet , die durch ein Slice definiert werden . Mit anderen Worten handelt es sich um eine Kombination aus Spitzen und Verbindungspunkten. Einführung – Ändern der Struktur einer Klasse und/oder Ändern der Vererbungshierarchie, um Aspektfunktionalität zu Fremdcode hinzuzufügen. Ziel ist das Objekt, auf das die Empfehlung angewendet wird. Beim Weben werden Aspekte mit anderen Objekten verknüpft, um die empfohlenen Proxy-Objekte zu erstellen. Dies kann zur Kompilierungszeit, Ladezeit oder Laufzeit erfolgen. Es gibt drei Arten des Webens:
  • Weben zur Kompilierungszeit – Wenn Sie über den Quellcode eines Aspekts und den Code verfügen, in dem Sie Aspekte verwenden, können Sie den Quellcode und den Aspekt direkt mit dem AspectJ-Compiler kompilieren.
  • Weben nach der Kompilierung (binäres Weben) – wenn Sie keine Quellcodetransformationen verwenden können oder wollen, um Aspekte in Ihren Code einzubinden, können Sie bereits kompilierte Klassen oder Gläser nehmen und Aspekte einfügen;
  • Das Weben zur Ladezeit ist einfach ein binäres Weben, das verzögert wird, bis der Klassenlader die Klassendatei lädt und die Klasse für die JVM definiert.
    Um dies zu unterstützen, sind ein oder mehrere „Weave-Klassenlader“ erforderlich. Sie werden entweder explizit von der Laufzeit bereitgestellt oder vom „Weaving Agent“ aktiviert.
AspectJ ist eine spezifische Implementierung von AOP- Paradigmen , die die Fähigkeit implementiert, übergreifende Probleme zu lösen. Die Dokumentation finden Sie hier .

Beispiele in Java

Um AOP besser zu verstehen, schauen wir uns als Nächstes kleine Beispiele der Hello World-Ebene an. Was ist AOP?  Grundlagen der aspektorientierten Programmierung - 4Ich möchte sofort darauf hinweisen, dass wir in unseren Beispielen das Weaving zur Kompilierungszeit verwenden werden . Zuerst müssen wir die folgende Abhängigkeit zu unserer pom.xml hinzufügen :
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
Zur Verwendung von Aspekten wird in der Regel ein spezieller Ajs- Compiler verwendet . IntelliJ IDEA verfügt standardmäßig nicht über es. Wenn Sie es als Anwendungscompiler auswählen, müssen Sie daher den Pfad zur AspectJ- Distribution angeben . Auf dieser Seite können Sie mehr über die Methode zur Auswahl von Ajs als Compiler lesen. Dies war die erste Methode und die zweite (die ich verwendet habe) bestand darin, das folgende Plugin zu pom.xml hinzuzufügen :
<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>
Danach empfiehlt es sich, erneut aus Maven zu importieren und mvn clean compile auszuführen . Kommen wir nun zu den Beispielen.

Beispiel Nr. 1

Lassen Sie uns eine Hauptklasse erstellen . Darin haben wir einen Startpunkt und eine Methode, die die an ihn übergebenen Namen in der Konsole ausgibt:
public class Main {

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

  public static void printName(String name) {
     System.out.println(name);
  }
}
Nichts Kompliziertes: Sie haben den Namen übergeben und ihn in der Konsole angezeigt. Wenn wir es jetzt ausführen, zeigt die Konsole Folgendes an:
Tolya Vova Sasha
Nun ist es an der Zeit, die Leistungsfähigkeit von AOP zu nutzen. Jetzt müssen wir einen Dateiaspekt erstellen . Es gibt zwei Arten: Die erste ist eine Datei mit der Erweiterung .aj , die zweite ist eine reguläre Klasse, die AOP- Funktionen mithilfe von Anmerkungen implementiert. Schauen wir uns zunächst eine Datei mit der Erweiterung .aj an :
public aspect GreetingAspect {

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

  before() : greeting() {
     System.out.print("Привет ");
  }
}
Diese Datei ähnelt in gewisser Weise einer Klasse. Lassen Sie uns herausfinden, was hier vor sich geht: Pointcut – ein Schnitt oder eine Reihe von Verbindungspunkten; Greeting() – der Name dieses Slice; : Ausführung – beim Ausführen von * – alle, Aufruf – Main.printName(..) – diese Methode. Als nächstes kommt der spezifische Ratschlag – before() – der ausgeführt wird, bevor die Zielmethode aufgerufen wird : Greeting() – das Slice, auf das dieser Ratschlag reagiert, und unten sehen wir den Hauptteil der Methode selbst, der in Java geschrieben ist Sprache, die wir verstehen. Wenn wir main mit diesem Aspekt ausführen, erhalten wir die folgende Ausgabe auf der Konsole:
Hallo Tolya, hallo Vova, hallo Sasha
Wir können sehen, dass jeder Aufruf der printName- Methode durch einen Aspekt geändert wurde. Schauen wir uns nun an, wie der Aspekt aussehen wird, allerdings als Java-Klasse mit Annotationen:
@Aspect
public class GreetingAspect{

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

  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Привет ");
  }
}
Nach der .aj- Aspektdatei ist alles klarer:
  • @Aspect gibt an, dass die gegebene Klasse ein Aspekt ist;
  • @Pointcut("execution(* Main.printName(String))") ist ein Schnittpunkt, der bei allen Aufrufen von Main.printName mit einem eingehenden Argument vom Typ String ausgelöst wird ;
  • @Before("greeting()") – Hinweis, der vor dem Aufruf des am Greeting()- Schnittpunkt beschriebenen Codes angewendet wird .
Wenn Sie main mit diesem Aspekt ausführen, ändert sich die Konsolenausgabe nicht:
Hallo Tolya, hallo Vova, hallo Sasha

Beispiel Nr. 2

Nehmen wir an, wir haben eine Methode, die einige Operationen für Clients ausführt, und rufen diese Methode von main aus auf :
public class Main {

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

  public static void makeSomeOperation(String clientName) {
     System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  }
}
Mithilfe der @Around- Annotation erstellen wir so etwas wie eine „Pseudo-Transaktion“:
@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("Операция не удалась, откат транзакции...");
     }
  }
  }
Mit der continue- Methode des ProceedingJoinPoint- Objekts rufen wir die Wrapper-Methode auf, um ihren Platz im Board und dementsprechend den Code in der obigen Methode joinPoint.proceed(); zu bestimmen. - das ist Vorher , was unten ist - Nachher . Wenn wir main ausführen, erhalten wir in der Konsole:
Öffnen einer Transaktion... Durchführen einiger Vorgänge für den Kunden - Tolya Schließen einer Transaktion....
Wenn wir unserer Methode einen Ausnahmeauslöser hinzufügen (plötzlich schlägt die Operation fehl):
public static void makeSomeOperation(String clientName)throws Exception {
  System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  throw new Exception();
}
Dann erhalten wir die Ausgabe in der Konsole:
Eine Transaktion öffnen... Einige Vorgänge für den Client ausführen - Tolya Der Vorgang ist fehlgeschlagen, die Transaktion wurde zurückgesetzt...
Es stellte sich heraus, dass es sich um eine Scheinverarbeitung des Scheiterns handelte.

Beispiel Nr. 3

Als nächstes Beispiel führen wir beispielsweise eine Anmeldung in der Konsole durch. Schauen wir uns zunächst Main an , wo unsere Pseudo-Geschäftslogik stattfindet:
public class Main {
  private String value;

  public static void main(String[] args) throws Exception {
     Main main = new Main();
     main.setValue("<некоторое Bedeutung>");
     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();
     }
  }
}
In main legen wir mit setValue den Wert der internen Variablen fest – value , dann nehmen wir mit getValue diesen Wert und in checkValue prüfen wir, ob dieser Wert länger als 10 Zeichen ist. Wenn ja, wird eine Ausnahme ausgelöst. Schauen wir uns nun den Aspekt an, mit dem wir die Funktionsweise von Methoden protokollieren:
@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);
  }
}
Was ist denn hier los? @Pointcut("execution(* *(..))") - stellt eine Verbindung zu allen Aufrufen aller Methoden her; @AfterReturning(value = "methodExecuting()", return = "returningValue") – Hinweis, der ausgeführt wird, nachdem die Zielmethode erfolgreich abgeschlossen wurde. Wir haben hier zwei Fälle:
  1. Wenn eine Methode einen Rückgabewert hat if (returningValue != null) {
  2. Wenn kein Rückgabewert vorhanden ist, sonst {
@AfterThrowing(value = "methodExecuting()", throwing = "Exception") - Hinweis, der im Fehlerfall ausgelöst wird, d. h. wenn eine Ausnahme von der Methode ausgelöst wird. Und dementsprechend erhalten wir durch Ausführen von main eine Art Protokollierung in der Konsole:
Die Methode – setValue, der Klasse – Main wurde erfolgreich ausgeführt. Die Methode – getValue, der Klasse – Main, wurde erfolgreich ausgeführt, mit dem Ergebnis der Ausführung – <irgendein Wert>. Die Methode – checkValue, der Klasse – Main, wurde mit einer Ausnahme abnormal beendet – java.lang.Exception Methode – main, Klasse-Main, stürzte mit einer Ausnahme ab – java.lang.Exception
Nun, da wir die Ausnahme nicht behandelt haben, erhalten wir auch ihren Stacktrace: Was ist AOP?  Grundlagen der aspektorientierten Programmierung - 5Über Ausnahmen und ihre Behandlung können Sie in diesen Artikeln lesen: Ausnahmen in Java und Ausnahmen und ihre Behandlung . Das ist alles für mich heute. Heute haben wir AOP kennengelernt und man konnte sehen, dass dieses Biest nicht so gruselig ist, wie es dargestellt wird. Auf Wiedersehen alle!Was ist AOP?  Grundlagen der aspektorientierten Programmierung - 6
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION