Implementieren einer benutzerdefinierten AOP-Annotation

Implementieren einer benutzerdefinierten Spring-AOP-Anmerkung

1. Einführung

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 Wesentlichenit is a way for adding behavior to existing code without modifying that code.

Für eine detaillierte Einführung in AOP gibt es Artikel zu AOPpointcuts undadvice. In diesem Artikel wird davon ausgegangen, dass wir bereits Grundkenntnisse haben.

Der AOP-Typ, den wir in diesem Artikel implementieren, ist annotationsbasiert. Dies ist uns möglicherweise bereits bekannt, wenn wir die Anmerkung von Spring@Transactionalverwendet haben:

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

The key here is non-invasiveness. Durch die Verwendung von Annotations-Metadaten wird unsere Kerngeschäftslogik nicht mit unserem Transaktionscode verschmutzt. Dies erleichtert das Überlegen, Umgestalten und Testen für sich.

Manchmal können Leute, die Spring-Anwendungen entwickeln, dies alsSpring 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 unsereMaven dependencieshinzu.

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
    1.5.2.RELEASE



    
        org.springframework.boot
        spring-boot-starter-aop
    

Beachten Sie, dass wir den AOP-Starter integriert haben, der die Bibliotheken abruft, die wir für die Implementierung 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. Erstellen wir unsere Anmerkung:

@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 Annotation@Targetgibt an, wo unsere Annotation anwendbar ist. Hier verwenden wirElementType.Method,, was bedeutet, dass es nur mit Methoden funktioniert. Wenn wir versuchen würden, die Annotation an einer anderen Stelle zu verwenden, könnte 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 übergreifendes Anliegen zusammenfasst. In unserem Fall handelt es sich um die Protokollierung der Ausführungszeit von Methoden. Alles, was es ist, ist eine Klasse, die mit@Aspect: versehen ist

@Aspect
@Component
public class ExampleAspect {

}

Wir haben auch die Annotation@Componenteingefü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 von unserer benutzerdefinierten Annotation eingefügt werden soll.

6. Erstellen unserer Punkte und Ratschläge

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.

Erstens haben wir unsere Methode mit@Around. kommentiert. Dies ist unser Rat, und um Rat 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.before undafter, die für diesen Artikel jedoch nicht berücksichtigt werden.

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

Die MethodelogExecutionTime() selbst ist unser Rat. Es gibt ein einziges Argument,ProceedingJoinPoint. In unserem Fall ist dies eine Ausführungsmethode, die mit@LogExecutionTime. kommentiert wurde

Schließlich, wenn unsere mit Annotationen versehene 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 vonproceed(),, bei dem nur die ursprüngliche Methode mit Anmerkungen aufgerufen wird.

7. Protokollierung unserer Ausführungszeit

Jetzt haben wir unser Skelett eingerichtet. Alles, was wir tun müssen, ist, unseren Ratschlägen eine zusätzliche Logik hinzuzufügen. Dies protokolliert die Ausführungszeit zusätzlich zum Aufrufen der ursprünglichen Methode. 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 getan, was hier besonders kompliziert ist. 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 Instanzjoinpointbereitgestellt wird. Wenn wir möchten, können wir auch auf andere Informationen zugreifen, z. B. Methodenargumente.

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

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

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

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

8. Fazit

In diesem Artikel haben wir Spring Boot AOP genutzt, um unsere benutzerdefinierte Anmerkung 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 aufover on GitHub verfügbar. Dies ist ein Maven-Projekt, das so ausgeführt werden sollte, wie es ist.