
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 :-
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.
-
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é.
-
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é.
- Les aspects sont du code séparé qui peut être amélioré et utilisé encore et encore.

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 :-
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() .
- 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() .
-
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) .
-
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) .
-
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() .
-
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".
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 :
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 :
@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() .
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 :
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 :
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 :
- Lorsque la méthode a une valeur de retour — if (returningValue! = Null) {
- Lorsqu'il n'y a pas de valeur de retour — else {


GO TO FULL VERSION