Le but de ce tutorial est de présenter l'utilisation de l'AOP jshadow. Pour cela nous allons modéliser un problème complétement inspiré de l'excellent livre Spring in Action.
Dans notre application, de vaillants chevaliers partiront dans de nombreuses quêtes tout plus périlleuses les unes que les autres ! Des dragons seront pourfendus et des princesses sauvées ! Les hauts faîts de ces nobles héros seront chantés par leurs fidèles ménestrels.
On commence tout d'abord par définir un chevalier en tant que POJO :
public class Chevalier
{
/**
* Le nom du chevalier
*/
private String nom;
public Chevalier(String nom)
{
this.nom = nom;
}
public String getNom()
{
return nom;
}
//d'autre propriétés diverses et variées (avec les getters et setters qui vont bien)
....
}
Vient ensuite une quête abstraite, avec un logger commons-logging (le ménéstrel). Bon on imaginera que l'implémentation de l'exception QueteException est connue, car sinon on ne s'en sortira pas..
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public abstract class Quete
{
/**
* commons-logging
*/
protected final static Log menestrel = LogFactory.get(Quete.class);
/**
* La difficulté de la quête
*/
private int difficulte;
/**
* Le nom de la quête
*/
private String nom;
public Quete(String nom, int difficulte)
{
this.nom = nom;
this.difficulte = difficule;
}
//d'autre propriétés diverses et variées (avec les getters et setters qui vont bien)
....
/**
* Un chevalier part en quête
*/
public abstract void partEnQuete(Chevalier chevalier) throws QueteException;
}
Réalisiont maitenant une implémentation concrété de cette abstraction de quête. Comme nos chevaliers n'ont pas froid aux yeux on les fait partir à la quête du Saint Graal(1)
public class QueteDuSaintGraal extends Quete
{
public QueteDuSaintGraal()
{
//c'est super dur comme quête....
super("la quête du Saint Graal", Integer.MAX_VALUE);
}
public void partEnQuete(Chevalier chevalier) throws QueteException;
{
//le menestrel chante le depart de la quête
if (menestrel.isInfoEnabled()) menestrel.info (String.format("Le chevalier %s part à %s", chevalier.getNom(), getNom());
//du code métier relatif à la quête
.....
}
}
Notre main ressemble alors à :
import org.apache.log4j.BasicConfigurator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* commons-logging
*/
protected final static Log log = LogFactory.get(MainClass.class);
public static void main(String[] args)
{
//configuration de log4j
BasicConfigurator.configure();
Quete queteSaintGraal = new QueteDuSaintGraal();
Chevalier lancelot = new Chevalier("Lancelot du lac");
try
{
queteSaintGraal.partEnQuete(lancelot);
}
catch(QueteException qe)
{
log.error(String.format("Le chevalier %s échoue dans %s", lancelot.getNom(), queteSaintGraal.getNom ()),e);
//... traitement de l'exception
}
}
En observant attentivement le code précédent on peut voir que
On utilisera dans cette version le Chevalier de la version précédente, car il est plutôt pas trop mal.
On commence par définir l'interface d'une quête :
On note immédiatement que l'objet Quete ne dépend PAS de commons logging.
public interface Quete
{
/**
* Un chevalier part en quête
*/
public void partEnQuete(Chevalier chevalier) throws QueteException;
}
public class QueteDuSaintGraal implements Quete
{
public void partEnQuete(Chevalier chevalier) throws QueteException;
{
//du code métier relatif à la quête
.....
}
///on imagine des getters et des setters pour diverses propriétées
...
}
On note rapidement que la QueteDuSaintGraal ne dépend toujours pas de commons logging et encore moins de jshadow. Une oeil attentif aura même vu qu'elle ne contient QUE du code métier relatif à la quête.
On définit maintenant un aspect dont le rôle sera de tracer les appels à la fonction partEnQuete. Les aspects dans jshadow sont des POJOs avec des méthodes qui possèdent une signature particulière ainsi que des annotations spécifiques.
On voit qu'il n'est pas nécessaire d'appeler une fonction "proceed()" sur l'objet MethodCall. L'appel à l'objet tissé est géré par le framework.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jshadow.aop.Pointcut;
import org.jshadow.aop.Intercept;
import org.jshadow.aop.When ;
import org.jshadow.aop.MethodCall;
public class Menestrel
{
/**
* commons-logging
*/
protected final static Log log = LogFactory.get(Menestrel.class);
//avec cette annotation on vise la méthode partEnQuete de la classe Quete
@Pointcut(target = Quete.class, name = "partEnQuete")
public void changeLouangeAuDepartDeLaQuete(MethodCall call)
{
//on récupére l'instance sur laquelle est invoquée la méthode
Quete quete = (Quete) call.getTarget();
//on récupére les objets passé en paramêtre
Chevalier chevalier = (Chevalier) call.getArgs()[0];
if (log.isInfoEnabled()) String.format ("Le chevalier %s part à %s", chevalier.getNom(), quete.getNom());
}
//en rajoutant l'annotation Intercept on indique que l'on se greffe à la fonction cible après l'émission d'une exception
@Intercept(when=When.EXCEPTION_THROWED)
@Pointcut(target = Quete.class, name = "partEnQuete")
public void changeLouangeLorsDeLEchec(MethodCall call)
{
//on récupére l'instance sur laquelle est invoquée la méthode
Quete quete = (Quete) call.getTarget();
//on récupére les objets passé en paramêtre
Chevalier chevalier = (Chevalier) call.getArgs()[0];
//l'object MethodCall contient l'exception qui à été lancer
log.error(String.format("Le chevalier %s échoue dans %s", chevalier.getNom(), quete.getNom()), call.getThrowable());
}
}
Le nom de la fonction aspect n'a absolument aucune importance. Il suffit qu'elle renvoie void et qu'elle prenne un unique argument de type MethodCall pour être valide.
Il est cependant de bon goût de lui donner un nom avec un rapport avec son code...
Tout cela est bien joli, mais doit certainement vous laissez dubitatif. Nous allons donc maintenant voir comment s'articule notre main.
import org.apache.log4j.BasicConfigurator ;
import org.jshadow.aop.Weaver;
public static void main(String[] args) throws QueteException
{
//configuration de log4j
BasicConfigurator.configure();
//instanciation d'un tisseur qui contiendra les aspects
Weaver tisseur = new Weaver();
//on tisse la toile d'aspect à partir d'une instance de notre aspect de log
tisseur.weave(new Menestrel());
//creation d'un objet proxy tissé qui encapsule notre implémentation de la quête du saint graal
Quete queteSaintGraal = weaver.createObject(Quete.class, new QueteDuSaintGraal());
Chevalier lancelot = new Chevalier("Lancelot du lac");
queteSaintGraal.partEnQuete(lancelot);
}
On note tout de suite que le main dépend :
(1) : plus de détails à http://fr.wikipedia.org/wiki/Graal