I. Prérequis▲
Vous pouvez utiliser l'IDE que vous souhaitez pour suivre ce tutoriel. Le code source des exemples présentés dans ce tutoriel est disponible dans ce repository Git, et plus particulièrement dans cette classe.
Les outils dont vous aurez besoin pour suivre ce tutoriel sont :
- JDK 8 d'Oracle ;
- Maven 3.
N'hésitez pas à lire le tutoriel Maven 3 si vous souhaitez en apprendre plus sur ce dernier.
II. Qu'est-ce que l'AOP ?▲
L'AOP (« Aspect Oriented Programming » ou « Programmation Orientée Aspect » en français) n'est pas à proprement parler un langage de programmation pour créer des programmes. C'est un concept qui permet d'exécuter une action lorsqu'un événement est déclenché (ex. : une méthode qui est exécutée). On peut donc voir l'AOP comme une extension au langage de programmation utilisé dans le projet.
III. Dans quel contexte utilise-t-on l'AOP ?▲
Lorsque l'on programme par aspect, on distingue le code métier du code technique. L'AOP permet de factoriser le code technique du projet. Le terme crosscutting concerns (ou « fonctionnalités transversales » en français) est utilisé pour désigner cette partie technique.
Pour citer quelques exemples de crosscutting concerns, l'AOP peut être utilisé pour :
- ouvrir/fermer des ressources (ex. : Démarrer et commiter/rollbacker une transaction) ;
- vérifier les permissions de l'utilisateur (sécurité) ;
- du cache ;
- des loggers ;
- du monitoring ;
- des benchmarks ;
- interrompre une méthode qui prend beaucoup de temps pour s'exécuter ;
- dans une moindre mesure : changer le comportement d'une méthode pour retourner un nouveau résultat.
Voici un exemple de code sans l'AOP :
public void addPermissionsToUser(Permission[] permissions, User user)
throws SQLException {
try {
// Code technique : SI aucune transaction n'est en cours ALORS
// Code technique : créer la transaction (cf: START TRANSACTION en SQL)
// Code technique : FIN SI
// Code métier : Ajouter des droits à l'utilisateur
} catch (SQLException e) {
// Code technique : SI une exception est lancée ALORS
// Code technique : rollbacker
// Code technique : propager l'exception
// Code technique : FIN SI
} finally {
// Code technique : SI une exception n'est pas lancée ET la méthode courante est la dernière méthode transactionnelle exécutée ALORS
// Code technique : commiter
// Code technique : FIN SI
}
}Voici un exemple de code avec l'AOP :
@Transactional
public void addPermissionsToUser(Permission[] permissions, User user) {
// Code métier : Ajouter des droits à l'utilisateur
}IV. Quelques dates sur l'AOP▲
- Juillet 1996 : la première apparition de l'AOP est attribuée à MTS (Microsoft Transaction Service) avec Windows NT 4.0, pour avoir mis en place un système d'interception pour les transactions et la sécurité.
-
L'AOP en Java avec AspectJ :
- 1997 : le projet AspectJ est créé par Gregor Kiczales et ses collègues, alors employés de la société Xerox PARC,
- 2001 : une première version publique est publiée,
- décembre 2002 : le projet est confié à la fondation Eclipse,
- 20 décembre 2005 : l'introduction des annotations dans Java 5 (4 octobre 2004) permet au projet AspectJ 5 d'avoir une nouvelle syntaxe à base d'annotations. Il est alors possible d'inclure du code AspectJ directement dans du code Java ;
- Le framework Spring intègre le concept d'AOP avec AspectJ, mais ce dernier limite les possibilités d'AspectJ à l'exécution des méthodes publiques.
V. AspectJ avec Spring▲
Il existe deux moyens pour mettre en place l'AOP avec AspectJ :
- En incluant directement les concepts AspectJ dans du code Java, votre code Java sera alors présent dans un fichier d'extension « .aj » et vous aurez besoin d'un plugin qui permettra à votre IDE de comprendre la syntaxe.
- En utilisant des annotations Java.
Dans ce tutoriel, nous verrons la deuxième solution.
V-A. Dépendances Maven▲
Ajoutez la dépendance ci-dessous dans votre pom.xml :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>Utilisez l'annotation @EnableAspectJAutoProxy pour activer l'AOP. Vous pouvez placer cette annotation sur votre classe main, ou bien créer une classe de configuration comme ci-dessous :
// Cette classe de configuration permet d'activer les aspects.
// Vous pouvez la laisser vide.
@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {
}V-B. Les annotations AspectJ▲
|
|
Crée un alias pour une portion de condition(s). |
|
|
S'exécute AVANT la méthode appelée. La méthode peut prendre en paramètre un JoinPoint. |
|
|
Similaire à, |
|
|
S'exécute lorsqu'une méthode est appelée, permet d'exécuter du code avant et après l'exécution de la méthode appelée (comme les filtres web). La méthode peut prendre en paramètre un ProceedingJoinPoint. |
V-C. Syntaxe AspectJ▲
Il faut placer les exemples ci-dessous dans les annotations (au choix) : @Pointcut, @Before, @Around, ou @After.
|
Template |
expression(<method scope> <return type> <fully qualified class name>.*(parameters)) |
|
Syntaxe représentant toutes les méthodes |
execution(* *(..)) |
|
Syntaxe représentant toutes les méthodes ayant une annotation |
@annotation(mypackage.MyAnnotation) && execution(* *(..)) |
V-D. Exemple avec les loggers▲
Créons une classe aspect « RestLogAspect » qui va afficher une ligne de log à chaque fois qu'une route, c'est-à-dire une méthode de notre annotée avec @RequestMapping, sera appelée.
Notre ligne de log aura le format suivant :
[REST][<durée-méthode-exécutée>ms][<nom-rest-contrôleur>][<nom-méthode-http>][<chemin-http>][<nom-méthode-contrôleur>()] <valeurs-des-paramètres>Ainsi, l'exécution de la méthode sayHello() du contrôleur ci-dessous :
@RestController
public class HelloWorldController {
@RequestMapping(path = "/hello/{name}", method = RequestMethod.GET)
public ResponseEntity<String> sayHello(@PathVariable("name") String name) {
return new ResponseEntity<>("hello " + name, HttpStatus.OK);
}
}… affichera ceci :
[REST][31ms][HelloWorldController][GET][/hello/{name}][sayHello()] worldImplémentation de notre classe (source) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
/**
* An aspect class for logging all routes
*
* @author Gokan EKINCI
*/
@Component
@Aspect
@Log
public class RestLogAspect {
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void restAnnotation() {}
@Around("restAnnotation() && execution(* *(..))")
public Object restLog(ProceedingJoinPoint joinPoint) throws Throwable {
final MethodSignature signature = (MethodSignature) joinPoint.getSignature();
final Method method = signature.getMethod();
final Class<?> controllerClass = joinPoint.getTarget().getClass();
final RequestMapping controllerAnnotation = controllerClass.getAnnotation(RequestMapping.class);
final String controllerRestPath = (controllerAnnotation.path().length == 0) ? "" : controllerAnnotation.path()[0];
final RequestMapping controllerMethodAnnotation = method.getAnnotation(RequestMapping.class);
final String methodRestPath = (controllerMethodAnnotation.path().length == 0) ? "" : controllerMethodAnnotation.path()[0];
final RequestMethod requestMethod = (controllerMethodAnnotation.method().length == 0) ? RequestMethod.GET : controllerMethodAnnotation.method()[0];
final Instant start = Instant.now();
// @Before code
Object returnValue = null;
Throwable throwable = null;
try {
returnValue = joinPoint.proceed();
} catch (Throwable throwable1) {
throwable = throwable1;
log.warning(String.format("[RestLogAspect] : %s", throwable1.getMessage()));
}
// @After code
final String logMessage = String.format("[REST][%sms][%s][%s][%s][%s()] %s",
Duration.between(start, Instant.now()).toMillis(),
controllerClass.getSimpleName(),
requestMethod,
controllerRestPath + methodRestPath,
method.getName(),
Arrays.stream(joinPoint.getArgs())
.map(argument -> (argument != null) ? argument.toString() : "null")
.collect(Collectors.joining(", "))
);
log.info(logMessage);
if (throwable != null) {
throw throwable;
}
return returnValue;
}}
Ce qu'il faut retenir de cette implémentation :
- Nous avons une classe annotée avec
@Componentet@Aspectpour indiquer à Spring que nous avons un bean de type aspect. L'annotation@Logest un simple utilitaire de la bibliothèque Lombok pour éviter de créer explicitement un attribut log de type Logger, n'en tenez pas compte. - Nous avons créé une méthode annotée avec
@PointCutpour créer un alias « restAnnotation() », que nous utiliserons dans l'annotation@Aroundde la méthode aspect «restLog() ». Cette étape est facultative, mais augmente la lisibilité de l'expression AspectJ utilisée dans l'annotation@Around. - Nous avons créé une méthode aspect
restLog() annotée avec@Around. Celle-ci va se déclencher lorsque l'expression indiquée dans l'annotation va matcher avec la méthode exécutée. Dans notre cas, lorsqu'une méthode annotée avec@RequestMappingva s'exécuter, notre méthode aspectrestLog()va s'exécuter aussi. - La méthode
restLog() possède en paramètre le type ProceedingJoinPoint pour récupérer toutes les informations nécessaires par rapport à la méthode exécutée.
VI. Remerciements▲
Je tiens à remercier ClaudeLeloup et -FloT- pour la relecture orthographique.




