Implementieren einer benutzerdefinierten Spring AOP-Annotation

1. Einleitung

In diesem Artikel implementieren wir im Frühjahr eine benutzerdefinierte AOP-Annotation mithilfe der AOP-Unterstützung.

Zunächst geben wir einen allgemeinen Überblick über AOP und erläutern, was es ist und welche Vorteile es bietet. Anschließend werden wir unsere Annotation Schritt für Schritt implementieren und schrittweise ein tieferes Verständnis der AOP-Konzepte aufbauen.

Das Ergebnis wird ein besseres Verständnis von AOP und die Möglichkeit sein, in Zukunft unsere benutzerdefinierten Frühlingsanmerkungen zu erstellen.

2. Was ist eine AOP-Anmerkung?

Kurz zusammengefasst steht AOP für aspektorientierte Programmierung. Im Wesentlichen ist dies eine Möglichkeit, vorhandenem Code Verhalten hinzuzufügen, ohne diesen Code zu ändern .

Für eine detaillierte Einführung in AOP gibt es Artikel zu AOP-Pointcuts und Ratschlägen. In diesem Artikel wird davon ausgegangen, dass wir bereits über Grundkenntnisse verfügen.

Die Art von AOP, die wir in diesem Artikel implementieren, ist annotationsgesteuert. Dies ist uns möglicherweise bereits bekannt, wenn wir die Annotation Spring @Transactional verwendet haben :

@Transactional public void orderGoods(Order order) { // A series of database calls to be performed in a transaction }

Der Schlüssel hier ist die Nichtinvasivität. Durch die Verwendung von Annotations-Metadaten wird unsere Kerngeschäftslogik nicht durch unseren Transaktionscode verschmutzt. Dies erleichtert das Überlegen, Refaktorieren und isolierte Testen.

Manchmal können Leute, die Spring-Anwendungen entwickeln, dies als " Spring Magic" betrachten, ohne genau darüber nachzudenken, wie es funktioniert. In Wirklichkeit ist das, was passiert, nicht besonders kompliziert. Sobald wir jedoch die Schritte in diesem Artikel ausgeführt haben, können wir unsere eigenen benutzerdefinierten Anmerkungen erstellen, um AOP zu verstehen und zu nutzen.

3. Maven-Abhängigkeit

Fügen wir zunächst unsere Maven-Abhängigkeiten hinzu.

In diesem Beispiel verwenden wir Spring Boot, da wir aufgrund der Konvention über den Konfigurationsansatz so schnell wie möglich einsatzbereit sind:

 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE    org.springframework.boot spring-boot-starter-aop  

Beachten Sie, dass wir den AOP-Starter integriert haben, der die Bibliotheken abruft, die wir zum Implementieren von Aspekten benötigen.

4. Erstellen unserer benutzerdefinierten Anmerkung

Die Annotation, die wir erstellen werden, wird verwendet, um die Zeit zu protokollieren, die eine Methode zur Ausführung benötigt. Lassen Sie uns unsere Anmerkung erstellen:

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LogExecutionTime { } 

Obwohl es sich um eine relativ einfache Implementierung handelt, ist es erwähnenswert, wofür die beiden Meta-Annotationen verwendet werden.

Die @ Target- Annotation gibt an, wo unsere Annotation anwendbar ist. Hier verwenden wir ElementType.Method, was bedeutet, dass es nur mit Methoden funktioniert. Wenn wir versuchen würden, die Annotation an einer anderen Stelle zu verwenden, kann unser Code nicht kompiliert werden. Dieses Verhalten ist sinnvoll, da unsere Annotation für die Ausführungszeit der Protokollierungsmethode verwendet wird.

Und @Retention gibt nur an, ob die Annotation zur Laufzeit für die JVM verfügbar ist oder nicht. Standardmäßig ist dies nicht der Fall, sodass Spring AOP die Anmerkung nicht sehen kann. Aus diesem Grund wurde es neu konfiguriert.

5. Unseren Aspekt schaffen

Jetzt haben wir unsere Anmerkung, lassen Sie uns unseren Aspekt erstellen. Dies ist nur das Modul, das unser Querschnittsthema zusammenfasst, in unserem Fall die Protokollierung der Ausführungszeit von Methoden. Alles, was es ist, ist eine Klasse, die mit @Aspect kommentiert ist:

@Aspect @Component public class ExampleAspect { }

Wir haben auch die Annotation @Component eingefügt , da unsere Klasse auch eine Spring Bean sein muss, um erkannt zu werden. Im Wesentlichen ist dies die Klasse, in der wir die Logik implementieren, die unsere benutzerdefinierte Annotation einfügen soll.

6. Erstellen Sie unseren Pointcut und Rat

Lassen Sie uns nun unseren Pointcut und unsere Ratschläge erstellen. Dies wird eine kommentierte Methode sein, die in unserem Aspekt lebt:

@Around("@annotation(LogExecutionTime)") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { return joinPoint.proceed(); }

Technisch gesehen ändert dies noch nichts am Verhalten, aber es ist noch viel los, was analysiert werden muss.

Zuerst haben wir unsere Methode mit @Around kommentiert . Dies ist unser Rat und bedeutet, dass wir vor und nach der Ausführung der Methode zusätzlichen Code hinzufügen. Es gibt andere Arten von Ratschlägen, z. B. vorher und nachher, aber sie werden für diesen Artikel nicht berücksichtigt.

Als nächstes hat unsere @ Round- Annotation ein Point-Cut-Argument. In unserem Pointcut heißt es nur: "Wenden Sie diesen Rat auf jede Methode an, die mit @LogExecutionTime kommentiert ist ." Es gibt viele andere Arten von Pointcuts, aber sie werden im Bereich wieder weggelassen.

Die Methode logExecutionTime () selbst ist unser Rat. Es gibt ein einziges Argument, ProceedingJoinPoint. In unserem Fall handelt es sich um eine Ausführungsmethode, die mit @LogExecutionTime kommentiert wurde .

Wenn schließlich unsere kommentierte Methode aufgerufen wird, wird unser Rat zuerst aufgerufen. Dann liegt es an unserem Rat, zu entscheiden, was als nächstes zu tun ist. In unserem Fall ist unser Rat nichts anderes als das Aufrufen von continue (), bei dem nur die ursprüngliche Methode mit Anmerkungen aufgerufen wird.

7. Protokollierung unserer Ausführungszeit

Jetzt haben wir unser Skelett an Ort und Stelle. Alles, was wir tun müssen, ist, unseren Ratschlägen eine zusätzliche Logik hinzuzufügen. Dies protokolliert zusätzlich zum Aufruf der ursprünglichen Methode die Ausführungszeit. Fügen wir dieses zusätzliche Verhalten unserem Rat hinzu:

@Around("@annotation(LogExecutionTime)") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); Object proceed = joinPoint.proceed(); long executionTime = System.currentTimeMillis() - start; System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms"); return proceed; }

Auch hier haben wir nichts besonders Kompliziertes getan. Wir haben gerade die aktuelle Zeit aufgezeichnet, die Methode ausgeführt und dann die Zeit gedruckt, die für die Konsole benötigt wurde. Wir protokollieren auch die Methodensignatur, die zur Verwendung der Joinpoint- Instanz bereitgestellt wird . Wenn wir möchten, können wir auch auf andere Informationen zugreifen, z. B. auf Methodenargumente.

Versuchen wir nun, eine Methode mit @LogExecutionTime zu kommentieren und dann auszuführen, um zu sehen, was passiert. Beachten Sie, dass dies eine Spring Bean sein muss, um richtig zu funktionieren:

@LogExecutionTime public void serve() throws InterruptedException { Thread.sleep(2000); }

Nach der Ausführung sollte Folgendes an der Konsole protokolliert werden:

void org.baeldung.Service.serve() executed in 2030ms

8. Fazit

In diesem Artikel haben wir Spring Boot AOP genutzt, um unsere benutzerdefinierte Annotation zu erstellen, die wir auf Spring Beans anwenden können, um ihnen zur Laufzeit zusätzliches Verhalten zu verleihen.

Der Quellcode für unsere Anwendung ist auf GitHub verfügbar. Dies ist ein Maven-Projekt, das so ausgeführt werden sollte, wie es ist.