JavaRush /Blog Java /Random-FR /Qu’est-ce que l’AOP ? Bases de la programmation orientée ...

Qu’est-ce que l’AOP ? Bases de la programmation orientée aspect

Publié dans le groupe Random-FR
Bonjour gars! Sans comprendre les concepts de base, il est assez difficile de se plonger dans les cadres et les approches de création de fonctionnalités. Nous allons donc parler aujourd'hui de l'un de ces concepts - AOP, ou programmation orientée aspect . Qu’est-ce que l’AOP ?  Fondamentaux de la programmation orientée aspect - 1Ce n’est pas un sujet facile et n’est pas souvent utilisé directement, mais de nombreux frameworks et technologies l’utilisent sous le capot. Et bien sûr, parfois lors d’entretiens, on vous demandera de vous dire en termes généraux de quel type d’animal il s’agit et où il peut être utilisé. Examinons donc les concepts de base et quelques exemples simples d'AOP en Java . Qu’est-ce que l’AOP ?  Fondamentaux de la programmation orientée aspect - 2Ainsi, l'AOP - programmation orientée aspect - est un paradigme visant à augmenter la modularité des différentes parties d'une application en séparant les préoccupations transversales. Pour ce faire, un comportement supplémentaire est ajouté au code existant, sans modifier le code d'origine. En d’autres termes, nous semblons ajouter des fonctionnalités supplémentaires aux méthodes et aux classes sans apporter de modifications au code modifié. Pourquoi est-ce nécessaire ? Tôt ou tard, nous arrivons à la conclusion que l'approche habituelle orientée objet ne peut pas toujours résoudre efficacement certains problèmes. A ce moment-là, AOP vient à la rescousse et nous donne des outils supplémentaires pour construire l'application. Et des outils supplémentaires signifient une flexibilité de développement accrue, grâce à laquelle il existe plus d'options pour résoudre un problème particulier.

Application de l'AOP

La programmation orientée aspect est conçue pour résoudre des problèmes transversaux, qui peuvent être n'importe quel code répété plusieurs fois de différentes manières, qui ne peut pas être complètement structuré dans un module séparé. En conséquence, avec AOP , nous pouvons laisser cela en dehors du code principal et le définir verticalement. Un exemple est l’application d’une politique de sécurité dans une application. En règle générale, la sécurité touche de nombreux éléments d’une application. De plus, la politique de sécurité de l’application doit être appliquée de manière égale à toutes les parties existantes et nouvelles de l’application. Parallèlement, la politique de sécurité utilisée peut elle-même évoluer. C'est là que l'utilisation de l'AOP peut s'avérer utile . Un autre exemple est la journalisation . Il existe plusieurs avantages à utiliser une approche de journalisation AOP par rapport à l'insertion manuelle de la journalisation :
  1. Le code de journalisation est facile à implémenter et à supprimer : il vous suffit d'ajouter ou de supprimer quelques configurations d'un certain aspect.
  2. Tout le code source de la journalisation est stocké au même endroit et il n'est pas nécessaire de rechercher manuellement tous les lieux d'utilisation.
  3. Le code destiné à la journalisation peut être ajouté n'importe où, qu'il s'agisse de méthodes et de classes déjà écrites ou de nouvelles fonctionnalités. Cela réduit le nombre d'erreurs des développeurs.
    De plus, lorsque vous supprimez un aspect d'une configuration de conception, vous pouvez être absolument sûr que tout le code de trace est supprimé et que rien ne manque.
  4. Les aspects sont du code autonome qui peut être réutilisé et amélioré encore et encore.
Qu’est-ce que l’AOP ?  Fondamentaux de la programmation orientée aspect - 3AOP est également utilisé pour la gestion des exceptions, la mise en cache et la suppression de certaines fonctionnalités pour le rendre réutilisable.

Concepts de base de l'AOP

Pour aller plus loin dans l’analyse du sujet, faisons d’abord connaissance avec les principaux concepts de l’AOP. Le conseil est une logique supplémentaire, un code, appelé depuis le point de connexion. Les conseils peuvent être effectués avant, après ou à la place du point de connexion (plus d'informations ci-dessous). Types de conseils possibles :
  1. Avant (Avant) - les conseils de ce type sont lancés avant l'exécution des méthodes cibles - points de connexion. Lorsque nous utilisons des aspects en tant que classes, nous utilisons l'annotation @Before pour marquer le type de conseil comme venant avant. Lorsque vous utilisez des aspects en tant que fichiers .aj , ce sera la méthode before() .
  2. After (After) - conseils exécutés après la fin de l'exécution des méthodes - points de connexion, à la fois dans les cas normaux et lorsqu'une exception est levée.
    Lorsque nous utilisons des aspects comme classes, nous pouvons utiliser l'annotation @After pour indiquer qu'il s'agit d'une astuce qui vient après.
    Lorsque vous utilisez des aspects en tant que fichiers .aj , ce sera la méthode after() .
  3. Après le retour - ces astuces ne sont exécutées que si la méthode cible fonctionne normalement, sans erreur.
    Lorsque les aspects sont représentés sous forme de classes, nous pouvons utiliser l' annotation @AfterReturning pour marquer les conseils comme étant exécutés une fois terminés avec succès.
    Lors de l'utilisation d'aspects en tant que fichiers .aj, ce sera la méthode after() renvoyant (Object obj) .
  4. Après le lancement - ce type de conseil est destiné aux cas où une méthode, c'est-à-dire un point de connexion, lève une exception. Nous pouvons utiliser ces conseils pour gérer l'échec de l'exécution (par exemple, annuler l'intégralité de la transaction ou journaliser avec le niveau de trace requis).
    Pour les classes d'aspect, l' annotation @AfterThrowing est utilisée pour indiquer que ce conseil est utilisé après la levée d'une exception.
    Lors de l'utilisation d'aspects sous forme de fichiers .aj , ce sera la méthode - after() lancée (Exception e) .
  5. Around est peut-être l'un des types de conseils les plus importants qui entourent une méthode, c'est-à-dire un point de connexion, avec lequel nous pouvons, par exemple, choisir d'exécuter ou non une méthode de point de connexion donnée.
    Vous pouvez écrire du code de conseil qui s'exécute avant et après l'exécution de la méthode de point de jointure.
    Les responsabilités du conseil incluent l'appel de la méthode du point de jointure et le renvoi des valeurs si la méthode renvoie quelque chose. Autrement dit, dans cette astuce, vous pouvez simplement imiter le fonctionnement de la méthode cible sans l'appeler et renvoyer quelque chose qui vous est propre.
    Pour les aspects sous forme de classes, nous utilisons l'annotation @Around pour créer des astuces qui enveloppent le point de connexion. Lorsque vous utilisez des aspects en tant que fichiers .aj , ce sera la méthode around() .
Point de jointure - un point dans un programme en cours d'exécution (appel d'une méthode, création d'un objet, accès à une variable) où les conseils doivent être appliqués. En d'autres termes, il s'agit d'une sorte d'expression régulière, à l'aide de laquelle sont trouvés des emplacements pour introduire du code (emplacements pour appliquer des astuces). Un pointcut est un ensemble de points de connexion . La coupe détermine si un point de connexion donné correspond à une pointe donnée. Aspect est un module ou une classe qui implémente des fonctionnalités de bout en bout. Un aspect modifie le comportement du reste du code en appliquant des conseils aux points de jointure définis par une tranche . En d’autres termes, il s’agit d’une combinaison d’astuces et de points de connexion. Introduction - modifier la structure d'une classe et/ou modifier la hiérarchie d'héritage pour ajouter des fonctionnalités d'aspect au code étranger. La cible est l'objet auquel le conseil sera appliqué. Le tissage est le processus de liaison d'aspects avec d'autres objets pour créer les objets proxy recommandés. Cela peut être fait au moment de la compilation, du chargement ou de l’exécution. Il existe trois types de tissage :
  • Tissage au moment de la compilation - Si vous disposez du code source d'un aspect et du code dans lequel vous utilisez les aspects, vous pouvez compiler le code source et l'aspect directement à l'aide du compilateur AspectJ ;
  • tissage post-compilation (tissage binaire) - si vous ne pouvez pas ou ne voulez pas utiliser de transformations de code source pour intégrer des aspects dans votre code, vous pouvez prendre des classes ou des fichiers jar déjà compilés et injecter des aspects ;
  • le tissage au moment du chargement est simplement un tissage binaire différé jusqu'à ce que le chargeur de classe charge le fichier de classe et définisse la classe pour la JVM.
    Pour prendre en charge cela, un ou plusieurs « chargeurs de classe de tissage » sont requis. Ils sont soit explicitement fournis par le runtime, soit activés par « l'agent de tissage ».
AspectJ est une implémentation spécifique des paradigmes AOP qui implémente la capacité à résoudre des problèmes transversaux. La documentation peut être trouvée ici .

Exemples en Java

Ensuite, pour mieux comprendre AOP, nous examinerons de petits exemples du niveau Hello World. Qu’est-ce que l’AOP ?  Fondamentaux de la programmation orientée aspect - 4Permettez-moi de noter immédiatement que dans nos exemples, nous utiliserons le tissage au moment de la compilation . Nous devons d’abord ajouter la dépendance suivante à notre pom.xml :
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
En règle générale, un compilateur Ajs spécial est utilisé pour utiliser les aspects . IntelliJ IDEA ne l'a pas par défaut, donc lorsque vous le choisissez comme compilateur d'application, vous devez spécifier le chemin d'accès à la distribution AspectJ . Vous pouvez en savoir plus sur la méthode de choix d'Ajs comme compilateur sur cette page. C'était la première méthode, et la seconde (que j'ai utilisée) consistait à ajouter le plugin suivant à 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>
Après cela, il est conseillé de réimporter depuis Maven et d'exécuter mvn clean compile . Passons maintenant aux exemples.

Exemple n°1

Créons une classe Main . Nous y aurons un point de lancement et une méthode qui imprime les noms qui lui sont transmis dans la console :
public class Main {

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

  public static void printName(String name) {
     System.out.println(name);
  }
}
Rien de compliqué : ils ont passé le nom et l'ont affiché dans la console. Si nous l'exécutons maintenant, la console affichera :
Tolya Vova Sacha
Eh bien, il est temps de profiter de la puissance de l’AOP. Nous devons maintenant créer un aspect fichier . Ils sont de deux types : le premier est un fichier avec l' extension .aj , le second est une classe standard qui implémente les fonctionnalités AOP à l'aide d'annotations. Regardons d'abord un fichier avec une extension .aj :
public aspect GreetingAspect {

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

  before() : greeting() {
     System.out.print("Привет ");
  }
}
Ce fichier est un peu similaire à une classe. Voyons ce qui se passe ici : pointcut - une coupe ou un ensemble de points de connexion ; Greeting() — le nom de cette tranche ; : exécution - lors de l'exécution de * - tous, appelez - Main.printName(..) - cette méthode. Vient ensuite le conseil spécifique - before() - qui est exécuté avant que la méthode cible ne soit appelée, : Greeting() - la tranche à laquelle réagit ce conseil, et ci-dessous nous voyons le corps de la méthode elle-même, qui est écrit en Java langue que nous comprenons. Lorsque nous exécutons main avec cet aspect présent, nous obtiendrons le résultat suivant sur la console :
Bonjour Tolya Bonjour Vova Bonjour Sasha
On voit que chaque appel à la méthode printName a été modifié par un aspect. Voyons maintenant à quoi ressemblera l'aspect, mais en tant que classe Java avec des annotations :
@Aspect
public class GreetingAspect{

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

  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Привет ");
  }
}
Après le fichier d'aspect .aj , tout est plus évident :
  • @Aspect indique que la classe donnée est un aspect ;
  • @Pointcut("execution(* Main.printName(String))") est un point de coupure qui se déclenche sur tous les appels à Main.printName avec un argument entrant de type String ;
  • @Before("greeting()") - conseil appliqué avant d'appeler le code décrit au point de coupure Greeting() .
Exécuter main avec cet aspect ne modifiera pas la sortie de la console :
Bonjour Tolya Bonjour Vova Bonjour Sasha

Exemple n°2

Disons que nous avons une méthode qui effectue certaines opérations pour les clients et que nous appelons cette méthode depuis main :
public class Main {

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

  public static void makeSomeOperation(String clientName) {
     System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  }
}
En utilisant l' annotation @Around , nous allons créer quelque chose comme une « pseudo-transaction » :
@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("Операция не удалась, откат транзакции...");
     }
  }
  }
En utilisant la méthode procéd de l' objet ProceedingJoinPoint , nous appelons la méthode du wrapper pour déterminer sa place dans le tableau et, par conséquent, le code dans la méthode ci-dessus joinPoint.proceed(); - c'est Avant , qui est en dessous - Après . Si nous exécutons main, nous obtiendrons dans la console :
Ouverture d'une transaction... Effectuer certaines opérations pour le client - Tolya Clôture d'une transaction....
Si nous ajoutons une exception à notre méthode (tout à coup, l'opération échoue) :
public static void makeSomeOperation(String clientName)throws Exception {
  System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  throw new Exception();
}
Ensuite, nous obtiendrons le résultat dans la console :
Ouverture d'une transaction... Effectuer certaines opérations pour le client - Tolya L'opération a échoué, la transaction a été annulée...
Il s’est avéré qu’il s’agissait d’un pseudo-traitement de l’échec.

Exemple n°3

Comme exemple suivant, faisons quelque chose comme nous connecter à la console. Tout d'abord, regardons Main , où se déroule notre pseudo logique métier :
public class Main {
  private String value;

  public static void main(String[] args) throws Exception {
     Main main = new Main();
     main.setValue("<некоторое meaning>");
     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();
     }
  }
}
Dans main , en utilisant setValue, nous définirons la valeur de la variable interne - value , puis en utilisant getValue, nous prendrons cette valeur et dans checkValue nous vérifierons si cette valeur comporte plus de 10 caractères. Si oui, une exception sera levée. Voyons maintenant l'aspect avec lequel nous allons enregistrer le fonctionnement des méthodes :
@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);
  }
}
Que se passe t-il ici? @Pointcut("execution(* *(..))") - se connectera à tous les appels à toutes les méthodes ; @AfterReturning(value = "methodExecuting()", return = "returningValue") - conseil qui sera exécuté une fois la méthode cible terminée avec succès. Nous avons ici deux cas :
  1. Lorsqu'une méthode a une valeur de retour if (returningValue != null) {
  2. Lorsqu'il n'y a pas de valeur de retour , else {
@AfterThrowing(value = "methodExecuting()", throwing = "exception") - conseil qui sera déclenché en cas d'erreur, c'est-à-dire lorsqu'une exception est levée depuis la méthode. Et en conséquence, en exécutant main , nous obtiendrons une sorte de journalisation dans la console :
La méthode - setValue, de la classe - Main a été exécutée avec succès. La méthode - getValue, de la classe - Main, a été exécutée avec succès, avec le résultat de l'exécution - <some value> La méthode - checkValue, de la classe - Main, s'est terminé anormalement avec une exception - méthode java.lang.Exception - main, class-Main, s'est écrasé avec une exception - java.lang.Exception
Eh bien, puisque nous n'avons pas géré l'exception, nous obtiendrons également sa trace de pile : Qu’est-ce que l’AOP ?  Fondamentaux de la programmation orientée aspect - 5Vous pouvez en savoir plus sur les exceptions et leur gestion dans ces articles : Exceptions en Java et Exceptions et leur gestion . C'est tout pour moi aujourd'hui. Aujourd'hui, nous avons fait connaissance avec AOP et vous avez pu constater que cette bête n'est pas aussi effrayante qu'elle est peinte. Au revoir tout le monde!Qu’est-ce que l’AOP ?  Fondamentaux de la programmation orientée aspect - 6
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION