1. Introduction

Ce tutoriel est un extrait de séances pratiques de la formation SPRING dispensée par Objis.

Objis

1. Prérequis

  • Installation JDK
  • Installation kit de développement AspectJ (AJDK)

3. Objectifs

  • Créer et mettre en œuvre un aspect de LOG
  • Comprendre le lien entre le compilateur aspectJ (ajc) et le compilateur Java (javac).
  • Déclarer un aspect, une coupe, un point de jonction avec AspectJ
  • Comprendre la valeur ajoutée de la programmation Aspects

4. Programme

  • Contexte :application bancaire
  • Partie 1 : tracer les retraits d'argent sans aspectJ.
  • Partie 2 : tracer les retraits d'argent avec un aspect AspectJ de log : LogAspect.aj
  • Partie 3 : Nouvelle version de l'aspect de LOG.
  • Partie 4 : Mise en œuvre d'un profiling

Durée totale : 40 min.

5. Contexte

Dans le cadre d'un projet d'envergure pour un établissement financier de la place, vous devez tracer les opérations de retrait d'argent de tout compte bancaire. Vous devez être capable de tracer l'état du compte AVANT le retrait et l'état du compte APRES le retrait.

6. Partie 1 : tracer sans aspectJ.

Fichiers à créer

Fichiers à créer

Créez un fichier CompteBancaire.java qui sera la classe métier représentant un compte bancaire. Ajoutez une propriété solde ainsi que getter/setter puis constructeur. Ajoutez enfin les méthodes de retrait et dépot.

 
Sélectionnez
package com.objis.demoaspectj.banque;

public class CompteBancaire {
	private int solde;
	
	public CompteBancaire(int solde) {
		super();
		this.solde = solde;
	}

	public void depot (int sommeDepot){
		solde = solde + sommeDepot;
	}

	public void retrait (int sommeRetrait){
		solde = solde - sommeRetrait;
	}

	public int getSolde() {
		return solde;
	}

	public void setSolde(int solde) {
		this.solde = solde;
	}
}

Créez un fichier Main.java qui sera la classe principale de l'application (il contiendra la méthode main()). Elle va instancier un compte Bancaire et réaliser une opération de retrait.

 
Sélectionnez
package com.objis.demoaspectj;

import com.objis.demoaspectj.banque.CompteBancaire;

public class Main {
	public static void main(String[] args) {
		// 1 : Création d'un compte avec solde initial
		CompteBancaire monCompte = new CompteBancaire(1000);

		// 2 : Retrait
		System.out.println("AVANT le retrait");
			
		// 3 : Retrait
		monCompte.retrait(300);

		// 4 : Retrait
		System.out.println("APRES le retrait");
	}
}

REMARQUE : sur les 4 étapes ci-dessus, :

  • 2 étapes ne concernent pas directement le 'métier'. En effet les étapes 2 et 4 sont liées à une préoccupation 'technique' : celle de tracer un évènement (ici le retrait).
  • 2 étapes concernent le métier. En effet les étapes 1 et 3 sont liées à des préocupation directement liées au métier bancaire : créer un compte et effectuer un retrait.

INFO : Le 'tisseur d'aspects' AspectJ va nous permettre par la suite d'isoler cette préocupation technique dans un fichier distinct : le fichier LogAspectj.aj qui représente l'aspect LOG.

Compilez les classes avec le compilateur javac (issu du kit de développement Java).

 
Sélectionnez
javac CompteBancaire.java Main.java

Vous obtenez ceci.

Fichiers compilés

Rangez les classes générés (Main.class et CompteBancaire.class) respectivement dans les répertoire com/objis/demoaspectj/ et com/objis/demoaspectj/banque.

Lancez l'éxécution de la classe principale : Main

 
Sélectionnez
java - cp .;lib\aspectjrt.jar com.objis.demoaspectj.Main
Avant le retrait
Après le retrait

Nous obtenons bien les traces qui précèdent et suivent l'appel à la méthode retrait().

Pour cela nous avons du écrire 'en dur' dans la classe cliente les lignes suivantes :

  • System.out.println("AVANT le retrait") ;
  • System.out.println("APRES le retrait") ;

Nous aurions également pu écrire ces ligne dans la méthode retrait() de la classe appelée (CompteBancaire).

Et s'il était possible d'afficher les mêmes traces sans écrire ces lignes ni dans la classe appelante (ici Main), ni dans la classe appelée ?

C'est là qu'intervient un tisseur d'aspect comme AspectJ.

7. Partie 2 : tracer avec aspectJ

Dans cette partie vous allez mettre le tisseur d'aspect AspectJ en action. Vous allez supprimer tout code de Log dans vos classes et centraliser la gestion des logs dans un aspect aspectJ : LogAspect.aj

Vous allez utiliser le compilateur ajc (surcouche du compilateur javac) pour compiler aussi bien l'aspect LogAspect.aj que les classes Main.java et CompteBancaire.java.

Modifiez le contenu de la classe Main de la façon suivante :

 
Sélectionnez
package com.objis.demoaspectj;

import com.objis.demoaspectj.banque.CompteBancaire;

public class Main {
	public static void main(String[] args) {
		// 1 : Création d'un compte avec solde initial
		CompteBancaire monCompte = new CompteBancaire(1000);
			
		// 2 : Retrait
		monCompte.retrait(300);
	}
}

Comme vous le constatez, il n'y a aucune ligne associée au Log . C'est un aspect LogAspect.aj que nous allons créer qui va 'intercepter' toute demande de retrait.

Tissage AspectJ

Créez un fichier LogAspect.aj et ajoutez le contenu suivant :

 
Sélectionnez
package com.objis.demoaspectj.aspects;

public aspect LogAspect { 
	pointcut logRetrait() 
	: execution(* com.objis.demoaspectj.banque.CompteBancaire.retrait(..));

	before() : logRetrait() { 
		System.out.println("Avant le retrait"); 
	} 

	after() : logRetrait() { 
		System.out.println("Après le retrait"); 
	} 
}

Explications :

  1. Vous déclarez un aspect à travers le mot clé 'aspect'. Ici l'aspect LogAspect.
  2. Vous déclarez une coupe nommée logRetrait() à travers le mot clé 'pointcut'. Une coupe est un ensemble de point de jonction (Moments d'exécution où il se passe qqchse qui vous interesse. C'est l'équivalent de point d'arrêt lors d'un débogage)
  3. Vous déclarez l'ensemble des points de jonction. Ici toute méthode retrait() de la classe com.objis.demoaspectj.banque.CompteBancaire, quelque soit le nombre de paramètre de la méthode retrait (..) et quelque soit le type de retour (*) de la méthode retrait().
  4. Vous déclarez un greffon type before() : le code System.out.println("AVANT le retrait") ; sera lancé juste avant tout point de jonction (càd ici toute exécution de la méthode retrait())
  5. Vous déclarez un greffon type after() : le code System.out.println("APRES le retrait") ; sera lancé juste après tout point de jonction (càd ici toute exécution de la méthode retrait())

ça y est : vous avez codé votre premier aspect 100% aspectJ. Reste à la compiler.

Compiler l'ensemble des classes Main.java, CompteBancaire.java et LogAspect.aj en utilisant le compilateur ajc (aspectj compiler) installé avec le kit de développement AspectJ (AJDK) :

 
Sélectionnez
ajc Main.java CompteBancaire.java LogAspect.aj

Vous obtenez ceci :

Fichiers compilés

Rangez les classes générés (Main.class, CompteBancaire.class et LogAspectj.class) respectivement dans les répertoire com/objis/demoaspectj/ , com/objis/demoaspectj/banque et com/objis/demoaspectj/aspects.

Remarque : la commande suivante crée pour vous l'arborescence :

 
Sélectionnez
ajc -d . Main.java CompteBancaire.java LogAspect.aj

Créez un répertoire 'lib' et ajoutez le jar aspectjrt présent dans ASPECTJ_HOME\lib . L'aspect aura besoin de ce jar à l'exécution.

Lancez l'exécution de la classe Main :

 
Sélectionnez
java - cp .;lib\aspectjrt.jar com.objis.demoaspectj.Main
Avant le retrait
Après le retrait

Le résultat est le même que dans la partie 1. Mais le code de notre classe principale est plus léger. Nous nous sommes concentré sur le métier et non sur une préoccupation de log.

8. Partie 3 : deuxième version de l'aspect

Expliquez l'effet de l'aspect suivant :

 
Sélectionnez
package com.objis.demoaspectj.aspects;

import com.objis.demoaspectj.banque.CompteBancaire;

public aspect LogAspect2 { 
	pointcut logRetrait(CompteBancaire compte, int sommeRetrait) : call(void com.objis.demoaspectj.banque.CompteBancaire.retrait(int))
				&& target(compte)
				&& args(sommeRetrait);
				
	before(CompteBancaire compte, int sommeRetrait) : logRetrait(compte, sommeRetrait) { 
		System.out.println("Avant le retrait de " + sommeRetrait + " euros du compte "  + compte); 
	} 

	after(CompteBancaire compte, int sommeRetrait) : logRetrait(compte, sommeRetrait) { 
		System.out.println("Après le retrait de " + sommeRetrait + " euros"); 
	} 
}

Analysez le résultat :

 
Sélectionnez
java - cp .;lib\aspectjrt.jar com.objis.demoaspectj.Main
Avant le retrait de 300 euros du compte com.objis.demoaspectj.banque.CompteBancaire@9304b1
Après le retrait de 300 euros

Vous découvrez ici une technique permettant de passer à un greffon le contexte d'exécution du point de jonction.

Expliquez l'effet du code suivant :

 
Sélectionnez
// Intercepter le constructeur de la classe CompteBancaire
pointcut constructeur() : call (CompteBancaire.new(..));

before () : constructeur(){
	System.out.println("Avant methode constructeur");
}

after () : constructeur(){
	System.out.println("Après methode constructeur");
}
}

Ajoutez ce code à l'aspect.

Exécutez.

9. Partie 4 : Aspect Profiling

Analysez le code suivant

 
Sélectionnez
package com.objis.demoaspectj.aspects;

public aspect ProfilingAspect {

	pointcut publicOperation() : execution(public * *.*(..));

	Object around() : publicOperation() {
  
    long debut = System.nanoTime();
    
    Object ret = proceed();
    
    long fin = System.nanoTime();
    
    System.out.println(thisJoinPointStaticPart.getSignature() + " a pris " + (fin-debut) + " nanoseconds");

    return ret;
	}
}
  • A quoi sert cet aspect ?
  • Ou est la coupe ?
  • Combien y a t'il de greffon ? de quel type ?

Comprendre thisJoinPointStaticPart

Remarque : la variable thisJoinPointStaticPart est une des 3 variables disponibles dans chaque greffon.

Cette variable apporte des informations à propos du point de jonction courant.

Exemples d'info :

  • signature de la méthode,
  • l'objet this,
  • les arguments de la méthode.

Mise en oeuvre

A VOUS DE JOUER : utilisez le compilateur ajc pour compiler cet aspect avec le programme principal.

Résultat attendu :

 
Sélectionnez
java - cp .;lib\aspectjrt.jar com.objis.demoaspectj.Main
Avant le retrait
Après le retrait
void com.objis.demoaspectj.banque.CompteBancaire.retrait(int) a pris 2250064 nanoseconds
void com.objis.demoaspectj.Main.main(String[]) a pris 90539444 nanoseconds

Expliquez

10. Conclusion

Dans ce tutoriel, vous avez mis en oeuvre AspectJ à travers la création d'un aspect LogAspect.aj

8 commentaires Donner une note à l'article (4.5)