Implémentation d’une annotation AOP de ressort personnalisée

Implémentation d'une annotation AOP de ressort personnalisée

1. introduction

Dans cet article, nous allons mettre en œuvre une annotation AOP personnalisée à l'aide de la prise en charge d'AOP dans Spring.

Tout d'abord, nous allons donner un aperçu de haut niveau de l'AOP, en expliquant ce qu'il est et ses avantages. Ensuite, nous mettrons en œuvre notre annotation étape par étape, en développant progressivement une compréhension plus approfondie des concepts AOP au fur et à mesure.

Le résultat sera une meilleure compréhension de l'AOP et la possibilité de créer nos annotations de printemps personnalisées à l'avenir.

2. Qu'est-ce qu'une annotation AOP?

Pour résumer rapidement, AOP est synonyme de programmation orientée aspect. Essentiellement,it is a way for adding behavior to existing code without modifying that code.

Pour une introduction détaillée à AOP, il y a des articles sur AOPpointcuts etadvice. Cet article suppose que nous possédons déjà des connaissances de base.

Le type d'AOP que nous allons implémenter dans cet article dépend des annotations. Nous sommes peut-être déjà familiers avec cela si nous avons utilisé l'annotation Spring@Transactional:

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

The key here is non-invasiveness. En utilisant les métadonnées d'annotation, notre logique métier principale n'est pas polluée par notre code de transaction. Cela facilite le raisonnement, la refactorisation et les tests isolés.

Parfois, les personnes qui développent des applications Spring peuvent voir cela commeSpring Magic ', sans réfléchir en détail à son fonctionnement. En réalité, ce qui se passe n’est pas particulièrement compliqué. Cependant, une fois que nous aurons terminé les étapes de cet article, nous serons en mesure de créer notre propre annotation personnalisée afin de comprendre et d'exploiter AOP.

3. Dépendance Maven

Tout d'abord, ajoutons nosMaven dependencies.

Pour cet exemple, nous utiliserons Spring Boot, car sa convention sur l'approche de configuration nous permet d'être opérationnel le plus rapidement possible:


    org.springframework.boot
    spring-boot-starter-parent
    1.5.2.RELEASE



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

Notez que nous avons inclus le démarreur AOP, qui extrait les bibliothèques dont nous avons besoin pour commencer à implémenter les aspects.

4. Création de notre annotation personnalisée

L'annotation que nous allons créer est une annotation qui sera utilisée pour consigner la durée d'exécution d'une méthode. Créons notre annotation:

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

}

Bien qu'il s'agisse d'une implémentation relativement simple, il convient de noter à quoi servent les deux méta-annotations.

L'annotation@Target nous indique où notre annotation sera applicable. Ici, nous utilisonsElementType.Method, ce qui signifie qu'il ne fonctionnera que sur les méthodes. Si nous essayions d'utiliser l'annotation ailleurs, notre code ne pourrait pas être compilé. Ce comportement est logique, car notre annotation sera utilisée pour enregistrer le temps d'exécution de la méthode.

Et@Retention indique simplement si l'annotation sera disponible pour la JVM au moment de l'exécution ou non. Par défaut, ce n'est pas le cas, donc Spring AOP ne pourra pas voir l'annotation. C’est pourquoi il a été reconfiguré.

5. Créer notre aspect

Maintenant que nous avons notre annotation, créons notre aspect. Ceci est juste le module qui encapsulera notre préoccupation transversale, ce qui est notre cas est la journalisation du temps d'exécution de la méthode. Tout ce que c'est, c'est une classe, annotée avec@Aspect:

@Aspect
@Component
public class ExampleAspect {

}

Nous avons également inclus l'annotation@Component, car notre classe doit également être un bean Spring pour être détectée. Il s’agit essentiellement de la classe dans laquelle nous allons implémenter la logique que nous voulons que notre annotation personnalisée injecte.

6. Créer notre point de vue et nos conseils

Maintenant, créons notre point de vue et nos conseils. Ce sera une méthode annotée qui vit dans notre aspect:

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

Techniquement, cela ne change pas encore le comportement de quoi que ce soit, mais il reste encore beaucoup à faire à analyser.

Tout d'abord, nous avons annoté notre méthode avec@Around.. Ceci est notre conseil, et autour des conseils signifie que nous ajoutons du code supplémentaire avant et après l'exécution de la méthode. Il existe d'autres types de conseils, tels quebefore etafter, mais ils seront exclus de cet article.

Ensuite, notre annotation@Around a un argument de coupure de point. Notre point de vue dit simplement: "Appliquez ce conseil à toute méthode annotée avec@LogExecutionTime." Il existe de nombreux autres types de pointcuts, mais ils seront à nouveau laissés de côté si la portée.

La méthodelogExecutionTime() elle-même est notre conseil. Il y a un seul argument,ProceedingJoinPoint. Dans notre cas, ce sera une méthode d'exécution qui a été annotée avec@LogExecutionTime.

Enfin, lorsque notre méthode annotée finira par être appelée, ce qui arrivera sera que notre conseil sera appelé en premier. Ensuite, c’est à nous de décider de la marche à suivre. Dans notre cas, notre conseil ne fait rien d'autre que d'appelerproceed(), qui est le simple appel de la méthode annotée d'origine.

7. Enregistrement de notre temps d'exécution

Maintenant que notre squelette est en place, tout ce que nous avons à faire est d’ajouter une logique supplémentaire à nos conseils. Ce sera ce qui enregistre le temps d'exécution en plus d'appeler la méthode d'origine. Ajoutons ce comportement supplémentaire à nos conseils:

@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;
}

Encore une fois, nous n’avons rien fait de particulièrement compliqué ici. Nous venons d’enregistrer l’heure actuelle, d’exécuter la méthode, puis d’imprimer le temps nécessaire à la console. Nous enregistrons également la signature de la méthode, qui est fournie pour utiliser l'instancejoinpoint. Nous pourrions également avoir accès à d'autres informations si nous le voulions, telles que les arguments de méthodes.

Maintenant, essayons d'annoter une méthode avec@LogExecutionTime,, puis de l'exécuter pour voir ce qui se passe. Notez que cela doit être un Spring Bean pour fonctionner correctement:

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

Après exécution, les éléments suivants devraient être consignés sur la console:

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

8. Conclusion

Dans cet article, nous avons utilisé Spring Boot AOP pour créer notre annotation personnalisée, que nous pouvons appliquer aux beans Spring pour leur injecter un comportement supplémentaire au moment de l'exécution.

Le code source de notre application est disponible surover on GitHub; c'est un projet Maven qui devrait pouvoir fonctionner tel quel.