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é 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é transversale 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, soit une méthode annotée avec @
RequestMapping
, de notre @
RestController
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()] world
Implé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
@
Component
et@
Aspect
pour indiquer à Spring que nous avons un bean de type aspect. L'annotation@
Log
est un simple utilitaire de librairie Lombok pour éviter de créer explicitement un attribut log de type Logger, n'en prenez pas compte. - Nous avons créé une méthode annotée avec
@
PointCut
pour créer un alias « restAnnotation() », que nous utiliserons dans l'annotation@
Around
de 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@
RequestMapping
va 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 XXX pour la relecture technique et XXX pour la relecture orthographique.