CodeGym/Blog Java/Random-FR/Qu'est-ce que l'AOP ? Principes de la programmation orien...
John Squirrels
Niveau 41
San Francisco

Qu'est-ce que l'AOP ? Principes de la programmation orientée aspect

Publié dans le groupe Random-FR
membres
Salut, les gars et les filles! Sans comprendre les concepts de base, il est assez difficile de se plonger dans les cadres et les approches pour créer des fonctionnalités. Donc, aujourd'hui, nous allons parler d'un de ces concepts - AOP, alias programmation orientée aspect . Qu'est-ce que l'AOP ?  Principes de la programmation orientée aspect - 1Ce sujet n'est pas facile et est rarement utilisé directement, mais de nombreux frameworks et technologies l'utilisent sous le capot. Et bien sûr, parfois lors d'entretiens, on peut vous demander de décrire en termes généraux de quelle sorte de bête il s'agit et où cela peut être appliqué. Jetons donc un coup d'œil aux concepts de base et à quelques exemples simples d' AOP en Java . Maintenant, AOP signifie programmation orientée aspect, qui est un paradigme destiné à 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 apporter de modifications au code d'origine. En d'autres termes, nous pouvons le considérer comme ajoutant des fonctionnalités supplémentaires aux méthodes et aux classes sans altérer le code modifié. Pourquoi est-ce nécessaire ? Tôt ou tard, nous concluons que l'approche orientée objet typique ne peut pas toujours résoudre efficacement certains problèmes. Et lorsque ce moment arrive, AOP vient à la rescousse et nous donne des outils supplémentaires pour créer des applications. Et des outils supplémentaires signifient une flexibilité accrue dans le développement de logiciels, ce qui signifie plus d'options pour résoudre un problème particulier.

Appliquer l'AOP

La programmation orientée aspect est conçue pour effectuer des tâches transversales, qui peuvent être n'importe quel code pouvant être répété plusieurs fois par différentes méthodes, qui ne peuvent pas être complètement structurés dans un module séparé. En conséquence, AOP nous permet de garder cela en dehors du code principal et de le déclarer verticalement. Un exemple est l'utilisation d'une politique de sécurité dans une application. En règle générale, la sécurité passe par de nombreux éléments d'une application. De plus, la politique de sécurité de l'application doit être appliquée de la même manière à toutes les parties existantes et nouvelles de l'application. Dans le même temps, une politique de sécurité en vigueur peut elle-même évoluer. C'est l'endroit idéal pour utiliser AOP . En outre, un autre exemple est la journalisation. Il y a plusieurs avantages à utiliser l'approche AOP pour la journalisation plutôt que d'ajouter manuellement une fonctionnalité de journalisation :
  1. Le code de journalisation est facile à ajouter et à supprimer : tout ce que vous avez à faire est d'ajouter ou de supprimer quelques configurations d'un certain aspect.

  2. Tout le code source de la journalisation est conservé au même endroit, vous n'avez donc pas besoin de rechercher manuellement tous les endroits où il est utilisé.

  3. Le code de journalisation peut être ajouté n'importe où, que ce soit dans des méthodes et des classes déjà écrites ou dans de nouvelles fonctionnalités. Cela réduit le nombre d'erreurs de codage.

    De plus, lors de la suppression d'un aspect d'une configuration de conception, vous pouvez être sûr que tout le code de traçage a disparu et que rien n'a été oublié.

  4. Les aspects sont du code séparé qui peut être amélioré et utilisé encore et encore.
Qu'est-ce que l'AOP ?  Principes de la programmation orientée aspect - 2AOP est également utilisé pour la gestion des exceptions, la mise en cache et l'extraction de certaines fonctionnalités afin de les rendre réutilisables.

Principes de base de l'AOP

Pour aller plus loin dans ce sujet, commençons par connaître les principaux concepts de l'AOP. Conseil — Logique ou code supplémentaire appelé à partir d'un point de jonction. Les conseils peuvent être effectués avant, après ou à la place d'un point de jonction (plus d'informations à leur sujet ci-dessous). Types de conseils possibles :
  1. Avant — ce type de conseil est lancé avant que les méthodes cibles, c'est-à-dire les points de jonction, ne soient exécutées. Lors de l'utilisation d'aspects en tant que classes, nous utilisons l' annotation @Before pour marquer le conseil comme venant avant. Lors de l'utilisation d'aspects en tant que fichiers .aj , ce sera la méthode before() .

  2. Après — les conseils qui sont exécutés une fois l'exécution des méthodes (points de jonction) terminée, à la fois en exécution normale et lors de la levée d'une exception.

    Lors de l'utilisation d'aspects en tant que classes, nous pouvons utiliser l' annotation @After pour indiquer qu'il s'agit d'un conseil qui vient après.

    Lors de l'utilisation d'aspects en tant que fichiers .aj , il s'agit de la méthode after() .

  3. Après le retour — ce conseil n'est exécuté que lorsque la méthode cible se termine normalement, sans erreur.

    Lorsque les aspects sont représentés sous forme de classes, nous pouvons utiliser l' annotation @AfterReturning pour marquer le conseil comme s'exécutant après une réussite.

    Lors de l'utilisation d'aspects en tant que fichiers .aj , ce sera la méthode de retour after() (Object obj) .

  4. After Throwing — ce conseil est destiné aux cas où une méthode, c'est-à-dire un point de jointure, lève une exception. Nous pouvons utiliser ce conseil pour gérer certains types d'échecs d'exécution (par exemple, pour annuler une transaction entière ou enregistrer avec le niveau de trace requis).

    Pour les aspects de classe, l' annotation @AfterThrowing est utilisée pour indiquer que ce conseil est utilisé après avoir levé une exception.

    Lors de l'utilisation d'aspects en tant que fichiers .aj , ce sera la méthode after() throwing (Exception e) .

  5. Autour — peut-être l'un des types de conseils les plus importants. Il entoure une méthode, c'est-à-dire un point de jonction que nous pouvons utiliser pour, par exemple, choisir d'exécuter ou non une méthode de point de jonction 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 jonction.

    Le conseil autour est responsable de l'appel de la méthode du point de jointure et des valeurs de retour si la méthode retourne quelque chose. En d'autres termes, dans ce conseil, vous pouvez simplement simuler le fonctionnement d'une méthode cible sans l'appeler et renvoyer ce que vous voulez comme résultat de retour.

    Étant donné les aspects en tant que classes, nous utilisons l' annotation @Around pour créer des conseils qui enveloppent un point de jonction. Lors de l'utilisation d'aspects sous la forme de fichiers .aj , cette méthode sera la méthode around() .

Point de jonction — le point dans un programme en cours d'exécution (c'est-à-dire l'appel de méthode, la création d'objet, l'accès aux variables) où les conseils doivent être appliqués. En d'autres termes, il s'agit d'une sorte d'expression régulière utilisée pour trouver des endroits pour l'injection de code (endroits où des conseils doivent être appliqués). Pointcut — un ensemble de points de jonction . Un point de coupure détermine si les conseils donnés sont applicables à un point de jonction donné. Aspect - un module ou une classe qui implémente des fonctionnalités transversales. Aspect modifie le comportement du code restant en appliquant des conseils aux points de jonction définis par certains pointcut . En d'autres termes, c'est une combinaison de conseils et de points de jonction. Introduction— changer la structure d'une classe et/ou changer la hiérarchie d'héritage pour ajouter la fonctionnalité de l'aspect au code étranger. Cible — l'objet auquel le conseil sera appliqué. Tissage - le processus de liaison d'aspects à d'autres objets pour créer des objets proxy conseillé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 avez le code source de l'aspect et le code où vous utilisez l'aspect, alors 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 les transformations du code source pour tisser des aspects dans le code, vous pouvez prendre des classes ou des fichiers jar précédemment compilés et leur injecter des aspects ;

  • Tissage au moment du chargement - il s'agit simplement d'un tissage binaire qui est retardé jusqu'à ce que le chargeur de classe charge le fichier de classe et définisse la classe pour la JVM.

    Un ou plusieurs chargeurs de classe de tissage sont nécessaires pour prendre en charge cela. Ils sont soit explicitement fournis par le runtime, soit activés par un "agent de tissage".

AspectJ - Une implémentation spécifique du paradigme AOP qui implémente la capacité d'effectuer des tâches transversales. La documentation peut être trouvée ici .

Exemples en Java

Ensuite, pour une meilleure compréhension d' AOP , nous examinerons de petits exemples de style "Hello World". Dès le départ, je noterai que nos exemples utiliseront le tissage au moment de la compilation . Tout d'abord, nous devons ajouter la dépendance suivante dans notre fichier pom.xml :
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
En règle générale, le compilateur spécial ajc est la façon dont nous utilisons les aspects. IntelliJ IDEA ne l'inclut pas par défaut, donc lorsque vous le choisissez comme compilateur d'application, vous devez spécifier le chemin d'accès à la distribution 5168 75 AspectJ . C'était la première voie. La seconde, qui est celle que j'ai utilisée, consiste à enregistrer le plugin suivant dans le fichier 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, c'est une bonne idée de réimporter depuis Maven et d'exécuter mvn clean compile . Passons maintenant directement aux exemples.

Exemple n°1

Créons une classe Main . Dans celui-ci, nous aurons un point d'entrée et une méthode qui imprime un nom passé sur la console :
public class Main {

  public static void main(String[] args) {
  printName("Tanner");
  printName("Victor");
  printName("Sasha");
  }

  public static void printName(String name) {
     System.out.println(name);
  }
}
Il n'y a rien de compliqué ici. Nous avons passé un nom et l'avons affiché sur la console. Si nous exécutons le programme maintenant, nous verrons ce qui suit sur la console :
Tanneur Victor Sasha
Maintenant, il est temps de profiter de la puissance de l'AOP. Nous devons maintenant créer un fichier d'aspect . Ils sont de deux types : le premier a l' extension de fichier .aj . La seconde est une classe ordinaire qui utilise des annotations pour implémenter les capacités AOP . Regardons d'abord le fichier avec l' extension .aj :
public aspect GreetingAspect {

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

  before() : greeting() {
     System.out.print("Hi, ");
  }
}
Ce fichier est un peu comme une classe. Voyons ce qui se passe ici : pointcut est un ensemble de points de jonction ; salutation() est le nom de ce point coupé ; : execution indique de l'appliquer lors de l'exécution de tous les ( * ) appels de la méthode Main.printName(...) . Vient ensuite un conseil spécifique — before() — qui est exécuté avant l'appel de la méthode cible. : greeting() est le point de coupure auquel ce conseil répond. Eh bien, et ci-dessous, nous voyons le corps de la méthode elle-même, qui est écrit dans le langage Java, que nous comprenons. Lorsque nous exécutons main avec cet aspect présent, nous obtiendrons cette sortie de console :
Salut, Tanner Salut, Victor Salut, Sasha
Nous pouvons voir que chaque appel à la méthode printName a été modifié grâce à un aspect. Voyons maintenant à quoi ressemblerait l'aspect 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("Hi, ");
  }
}
Après le fichier d'aspect .aj , tout devient plus évident ici :
  • @Aspect indique que cette classe est un aspect ;
  • @Pointcut("execution(* Main.printName(String))") est le point de coupure qui est déclenché pour tous les appels à Main.printName avec un argument d'entrée dont le type est String ;
  • @Before("greeting()") est un conseil qui est appliqué avant d'appeler le code spécifié dans le point de coupure de salutation() .
L'exécution de main avec cet aspect ne modifie pas la sortie de la console :
Salut, Tanner Salut, Victor Salut, Sasha

Exemple n° 2

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

  public static void main(String[] args) {
  performSomeOperation("Tanner");
  }

  public static void performSomeOperation(String clientName) {
     System.out.println("Performing some operations for Client " + clientName);
  }
}
Utilisons l' annotation @Around pour créer une "pseudo-transaction":
@Aspect
public class TransactionAspect{

  @Pointcut("execution(* Main.performSomeOperation(String))")
  public void executeOperation() {
  }

  @Around(value = "executeOperation()")
  public void beforeAdvice(ProceedingJoinPoint joinPoint) {
     System.out.println("Opening a transaction...");
     try {
        joinPoint.proceed();
        System.out.println("Closing a transaction...");
     }
     catch (Throwable throwable) {
        System.out.println("The operation failed. Rolling back the transaction...");
     }
  }
  }
Avec la méthode Proceed de l' objet ProceedingJoinPoint , nous appelons la méthode Wrapping pour déterminer son emplacement dans le conseil. Par conséquent, le code de la méthode ci-dessus joinPoint.proceed(); est Before , tandis que le code en dessous est After . Si nous exécutons main , nous obtenons ceci dans la console :
Ouverture d'une transaction... Réalisation de certaines opérations pour le Client Tanner Clôture d'une transaction...
Mais si nous lançons une exception dans notre méthode (pour simuler une opération ayant échoué) :
public static void performSomeOperation(String clientName) throws Exception {
  System.out.println("Performing some operations for Client " + clientName);
  throw new Exception();
}
Ensuite, nous obtenons cette sortie de console :
Ouverture d'une transaction... Exécution de certaines opérations pour le client Tanner L'opération a échoué. Annulation de la transaction...
Nous nous sommes donc retrouvés avec une sorte de capacité de gestion des erreurs.

Exemple n°3

Dans notre exemple suivant, faisons quelque chose comme se connecter à la console. Tout d'abord, jetez un œil à Main , où nous avons ajouté une pseudo logique métier :
public class Main {
  private String value;

  public static void main(String[] args) throws Exception {
     Main main = new Main();
     main.setValue("<some value>");
     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 , nous utilisons setValue pour attribuer une valeur à la variable d'instance value . Ensuite, nous utilisons getValue pour obtenir la valeur, puis nous appelons checkValue pour voir si elle contient plus de 10 caractères. Si tel est le cas, une exception sera levée. Voyons maintenant l'aspect que nous allons utiliser pour enregistrer le travail 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("Successful execution: method — %s method, class — %s class, return value — %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName(),
              returningValue);
     }
     else {
        System.out.printf("Successful execution: method — %s, class — %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName());
     }
  }

  @AfterThrowing(value = "methodExecuting()", throwing = "exception")
  public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
     System.out.printf("Exception thrown: method — %s, class — %s, exception — %s\n",
           joinPoint.getSignature().getName(),
           joinPoint.getSourceLocation().getWithinType().getName(),
           exception);
  }
}
Que se passe t-il ici? @Pointcut("execution(* *(..))") joindra tous les appels de toutes les méthodes. @AfterReturning(value = "methodExecuting()", return = "returningValue") est un conseil qui sera exécuté après l'exécution réussie de la méthode cible. Nous avons ici deux cas :
  1. Lorsque la 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") est un conseil qui sera déclenché en cas d'erreur, c'est-à-dire lorsque la méthode lève une exception. Et en conséquence, en exécutant main , nous obtiendrons une sorte de journalisation basée sur la console :
Exécution réussie : method — setValue, class — Main Exécution réussie : method — getValue, class — Main, return value — <une valeur> Exception levée : method — checkValue, class — Exception principale — java.lang.Exception Exception levée : method — main, classe — Main, exception — java.lang.Exception
Et puisque nous n'avons pas géré les exceptions, nous aurons toujours une trace de la pile : Qu'est-ce que l'AOP ?  Principes de la programmation orientée aspect - 3Vous pouvez lire sur les exceptions et la gestion des exceptions dans ces articles : Exceptions en Java et Exceptions : attraper et gérer . C'est tout pour moi aujourd'hui. Aujourd'hui, nous nous sommes familiarisés avec AOP , et vous avez pu constater que cette bête n'est pas aussi effrayante que certains le prétendent. Au revoir tout le monde!
Commentaires
  • Populaires
  • Nouveau
  • Anciennes
Tu dois être connecté(e) pour laisser un commentaire
Cette page ne comporte pas encore de commentaires