I. Introduction▲
I-A. Préambule▲
Spring est un framework créé et supporté par l'entreprise SpringSource (qui fait maintenant partie de VMware). Il permet de simplifier le développement d'applications Java et est devenu un standard dans l'industrie du développement logiciel basé sur la plateforme Java, surtout dans le développement Java EE.
Spring est un conteneur léger qui facilite le développement avec des POJO (Plain Old Java Object), c'est-à-dire avec des classes Java qui n'ont pas besoin de s'exécuter dans un conteneur spécifique ou dans un serveur d'applications. Il se compose d'un noyau (core) et de plusieurs modules qui ajoutent des fonctionnalités.
Dans ce tutoriel nous utiliserons l'inversion de contrôle (souvent connue comme IoC qui est son acronyme en anglais) et la programmation orientée aspects (AOP est son acronyme en anglais). Les acronymes en anglais étant les plus utilisés par les développeurs, nous les utiliserons tout au long du tutoriel.
I-B. Ce que nous allons faire▲
Nous allons :
- découvrir ce qu'est l'inversion de contrôle (IoC) et la programmation orientée aspects (AOP) avec des exemples simples ;
- configurer et utiliser Spring 3 dans une application Java autonome (stand-alone) ;
- utiliser l'IoC ainsi que l'AOP (en utilisant des annotations AspectJ) pour faire du jardinage ;
- configurer Spring avec un fichier XML et des annotations pour faire de l'IoC et créer ainsi les objets d'un jardin en y ajoutant des plantes de façon déclarative ;
- utiliser AOP pour qu'une mauvaise herbe capture les ajouts de nutriments aux plantes et les leur vole ;
- configurer les dépendances et construire le projet avec Maven 2.
I-C. Ce que nous n'allons pas faire▲
Nous n'allons pas :
- proposer ce document comme une référence exhaustive, mais plutôt comme un tutoriel introductif ;
- faire un cours de POO ou de design patterns ;
- coder des tests unitaires bien que cela soit indispensable dans les projets réels plus complexes ;
- décrire l'installation d'un IDE ou de Maven 2.
I-D. Prérequis▲
Mis à part des connaissances basiques du langage Java certains logiciels sont nécessaires :
- Java SDK version 6 ou ultérieure ;
- Maven 2 version 2.2.x ou ultérieure ;
- connexion Internet pour télécharger les dépendances ;
- optionnellement un IDE supportant l'ouverture de projets Maven 2 (SpringSource Tool Suite 2, Eclipse 3.5 avec le plug-in m2eclipse ou NetBeans 6.7 par exemple).
II. Introduction à l'IoC▲
L'inversion de contrôle permet de laisser à un conteneur comme Spring la responsabilité de gérer le cycle de vie des objets d'une application plutôt que de le faire manuellement. Sans IoC il est nécessaire de faire la création d'objets avec des new ce qui augmente les dépendances entre classes concrètes ; avec l'IoC on essaye de ne référencer que des interfaces ou des classes plus génériques ce qui permet d'avoir un code plus propre, réutilisable et facilement testable.
Prenons comme exemple un pot qui contient une plante :
/**
* Pot qui contient une plante.
*/
public
class
Pot {
/** Plante dans le pot */
private
Plante plante;
/** Constructeur qui crée une plante spécifique: pas d'IoC*/
public
Pot
(
) {
plante =
new
Tomate
(
);
}
/** Constructeur avec plante: compatible IoC*/
public
Pot
(
Plante plante) {
this
.plante =
plante;
}
}
Quand le Pot a la responsabilité de créer la Tomate, il y a un couplage fort avec cette classe concrète et il sera très difficile de réutiliser le code pour contenir une autre plante. Par contre si la plante est injectée dans le constructeur (ou via un setter) par une entité externe, celle-ci pourrait être un plant de tomate, une rose ou même des orties et le pot pourrait les contenir sans connaître le type exact de ces plantes.
Spring permettrait ici de déclarer d'un côté un bean (objet géré par Spring) tomate et d'un autre un bean pot. Lors de la création du pot, il lui injecterait la tomate. Si un jour on décide d'injecter une rose plutôt qu'une tomate, le code de la classe Pot restera inchangé il faudra juste modifier la configuration de Spring. Spring essaye d'être non intrusif et pour utiliser l'IoC, la classe Pot n'aurait pas besoin de changer ou d'avoir une dépendance avec Spring, car ce ne sont pas les beans qui demandent à Spring de leur fournir leurs dépendances, c'est Spring qui sait quand les injecter. C'est ce qui est souvent appelé le principe d'Hollywood : « Ne nous appelez pas, c'est nous qui vous appellerons ».
III. Introduction à AOP▲
L'AOP ou Programmation Orientée Aspects permet de séparer les aspects qui se retrouvent de manière transverse dans différentes parties d'une application (cross-cutting concerns) permettant ainsi de minimiser le code répétitif qui devient complexe et peu maintenable. L'AOP s'utilise souvent pour écrire des Logs, mesurer les performances ou pour la gestion de transactions.
Considérons la situation suivante.
Une alarme est une classe qui représente un dispositif électronique qui se met à sonner quand le taux d'humidité de la terre devient trop faible. Quand l'alarme sonne, le jardinier doit arriver en courant pour arroser ses plantes avant qu'elles ne sèchent (ce sont des plantes très fragiles). Il arrive aussi que de temps en temps un client vienne lui acheter une plante ou lui demander ses services et sonne à la porte.
Imaginons maintenant que le jardinier ait acheté un téléphone mobile avec une connexion illimitée à Internet. Il aimerait recevoir un tweet (post sur Twitter) chaque fois que l'alarme sonne ou qu'un client le cherche pour pouvoir se promener sans risques de manquer quelque chose. Il se fait installer dans chaque appareil une puce qui va poster automatiquement les tweets. Les versions 2.0 de l'Alarme et du Carillon pourraient être les suivantes :
/**
* Alarme qui indique une diminution du taux d'humidité et poste un tweet avec la puce.
*/
class
Alarme {
TweetChip tweetChip;
public
void
sonner
(
) {
/* L'alarme hurle */
//poste un tweet avec la puce
tweetChip.postTweet
(
);
}
}
/**
* Carillon qui poste un tweet avec la puce quand un client sonne.
*/
class
Carillon {
TweetChip tweetChip;
public
void
sonner
(
) {
//poste un tweet avec la puce
tweetChip.postTweet
(
);
}
}
Mais le jardinier sait qu'il pourrait avoir bientôt besoin que d'autres appareils et senseurs puissent aussi le prévenir à distance et qu'il devrait à chaque fois les modifier et les coupler à la TweetChip. La solution est chère et pas toujours possible quand les appareils sont propriétaires et impossible à ouvrir et modifier. Une meilleure idée lui vient à l'esprit : relier une seule puce à un microphone et quand ce dernier capte le son de n'importe quelle sonnerie, la première envoie un tweet. Comme ça plus besoin de modifier les appareils, ils restent intacts et plus besoin d'acheter de puce. Le jardinier a découvert l'AOP !
Avec l'AOP un aspect serait l'envoi de tweets et il serait déclenché quand une sonnerie sonne. D'un point de vue programmatique, l'invocation de la méthode sonner() de n'importe quelle classe serait détectée et à ce moment-là une autre action serait réalisée. Ainsi dans cet exemple on retrouve les concepts de l'AOP :
- Advice : le post du tweet en termes d'AOP s'appelle advice. L'advice définit l'action à effectuer lors de l'exécution de l'aspect ;
- Point de coupe (Pointcut) : le post des tweets se fait une fois qu'un senseur détecte quelque chose et exécute la méthode prevenir. Le pointcut décrit les différents points de jonction des aspects ;
- Advisor : le micro est le dispositif qui permet la détection d'un point de jonction, c'est l'advisor ;
- Point de jonction (Joinpoint) : les tweets pourraient se poster avant, après ou avant et après l'exécution d'une certaine méthode sur un objet, un bean Spring, ou seulement si cette méthode lance une exception ou contient certains arguments. Le point dans le code où l'advice est exécuté s'appelle joinpoint ;
- Cible (Target) : dans cet exemple, l'Alarme et le Carillon sont les objets sur lesquels sera tissé l'aspect. Ce sont les cibles (Target en anglais) de l'aspect.
IV. Développement de Spring Garden▲
IV-A. Scénario▲
Nous allons modéliser un joli jardin de printemps. Le jardin se compose d'un verger et d'un potager qui contiennent chacun d'eux des plantes et d'un jardinier qui s'en occupe.
Les plantes peuvent être arrosées et on peut aussi leur mettre de l'engrais. Chaque plante a un compteur qui contient les valeurs d'eau et d'engrais qu'elle a reçues. Dans ce jardin il y a cependant un problème : une mauvaise herbe rend malades quelques plantes et leur vole l'eau et l'engrais qui leur sont destinés. Le jardinier pense nourrir une plante, mais c'est la mauvaise herbe qui en profite. Ni les plantes, ni le jardinier ne connaissent l'existence de cette mauvaise herbe, mais celle-ci est bien présente.
IV-B. Structure du projet▲
Le projet suit la structure standard de dossiers de Maven 2 en plaçant le fichier pom.xml de configuration dans le dossier racine, les fichiers source Java dans le dossier src/main/java et le fichier XML de configuration de Spring dans le dossier src/main/resources.
Un seul package est utilisé : org.yannart.springgarden qui contient toutes les interfaces et classes.
IV-C. Classes et Beans▲
Le nom des classes est autodescriptif. La seule classe qui est isolée est SpringGardenApplication et ne sert qu'à démarrer le contexte de Spring. Il faut remarquer que les classes n'ont des références que vers des interfaces ce qui permet de découpler les implémentations.
Le diagramme ci-dessous montre les classes de l'application :
Les beans Springs sont configurés dans le fichier applicationContext.xml et à travers d'annotations Java. Un bean doit être vu comme un objet managé par Spring. Un bean ne correspond pas forcément à une seule instance, car, par exemple, un bean peut être de type prototype et à chaque fois qu'il est injecté dans un autre bean, une nouvelle instance est créée. Le diagramme des beans Spring montré ci-dessous a été généré avec le plugin Spring IDE pour Eclipse. Le diagramme montre les relations de composition et d'héritage entre beans. Si on compare le diagramme avec le fichier XML on peut observer que la cardinalité n'est pas représentée, par exemple le bean choux est seulement représenté une seule fois alors que le potager en contient deux. Il faut aussi remarquer que les beans déclarés avec des annotations sont bien représentés.
IV-C-1. Classe d'entrée de l'application▲
La classe SpringGardenApplication est le point d'entrée de cette application stand-alone. Sa méthode main charge le contexte de Spring.
import
org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Classe qui initialise l'application SpringGarden.
*/
public
class
SpringGardenApplication {
/**
* Methode main ou commence le flux de l'application.
*/
public
static
void
main
(
String[] args) {
// Demarre le contexte de Spring
new
ClassPathXmlApplicationContext
(
"applicationContext.xml"
,
SpringGardenApplication.class
);
}
}
Spring cherchera pour sa configuration le fichier applicationContext.xml qui se trouve dans le même package que la classe. Il est aussi possible d'utiliser une autre classe de Spring pour spécifier un fichier de configuration en dehors du classpath, par exemple vers un chemin absolu du système de fichiers.
IV-C-2. Fichier de configuration de Spring▲
Le fichier XML nommé applicationContext.xml, configure Spring en déclarant non seulement les beans métier de l'application, mais aussi des beans utilitaires pour, par exemple, configurer la détection de beans déclarés avec des annotations.
Il est conseillé d'utiliser les schémas XML de Spring plutôt que les DTD, car ils offrent un meilleur niveau de précision et une gestion des namespaces qui aident à la validation des XML et à une meilleure autocomplétion avec des éditeurs tels qu'Eclipse. Les schémas à déclarer varient en fonction des modules de Spring utilisés.
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns
=
"http://www.springframework.org/schema/beans"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns
:
aop
=
"http://www.springframework.org/schema/aop"
xmlns
:
context
=
"http://www.springframework.org/schema/context"
xmlns
:
p
=
"http://www.springframework.org/schema/p"
xsi
:
schemaLocation
=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"
>
<!-- ANNOTATIONS POUR L'INJECTION DE DEPENDANCES -->
<
context
:
annotation-config />
<!-- ANNOTATIONS POUR LA CREATION DE BEANS -->
<
context
:
component-scan
base-package
=
"org.yannart.springgarden"
/>
<!-- ANNOTATIONS POUR AOP -->
<
aop
:
aspectj-autoproxy />
Entre les balises <beans> et </beans> on déclare les beans qui seront gérés par Spring. Certaines balises correspondent à des raccourcis de configuration et tant que c'est possible, les balises <bean> seront utilisées pour déclarer des beans des classes propres à l'application.
La balise <context:annotation-config /> permet à Spring de détecter lors de la création de beans, leurs propriétés annotées avec des annotations comme @Autowire ou @Resource et devant être injectées. En fonction de l'annotation utilisée et de sa configuration, le bean à injecter sera cherché par nom d'identifiant ou par type.
La balise <context:component-scan base-package=« org.yannart.springgarden » /> spécifie à Spring de scanner les classes dans le classpath se contenant dans le package org.yannart.springgarden ou dans un sous-package en cherchant des annotations qui déclarent des beans. Ces annotations peuvent être entre autres @Component ou @Service. Spring génère un identifiant par défaut en enlevant la majuscule du nom de la classe annotée, il est bien sûr possible de spécifier un autre identifiant. Il faut remarquer que l'une des limitations de la configuration des beans avec des annotations plutôt que dans un fichier XML est que pour une classe Java donnée on ne peut configurer qu'un seul bean Spring alors que quelquefois ce n'est pas ce que l'on veut faire. Par exemple les beans qui représentent les différentes plantes du jardin instancient tous la même classe, mais configurent sa propriété nomPlante différemment ce qui n'est pas faisable avec des annotations.
La balise <aop:aspectj-autoproxy /> permet de spécifier à Spring de vérifier dans les classes des beans managés si des annotations AspectJ sont présentes et de les utiliser pour faire du tissage d'aspects.
IV-C-3. Annotations vs. XML▲
On a remarqué que les beans peuvent être déclarés soit dans un fichier XML soit avec des annotations dans les sources Java. Il faut faire attention aux fanatiques pro-XML ou pro-annotations qui veulent toujours utiliser exclusivement une façon de faire et essayer plutôt de faire le choix en fonction de leurs avantages et limitations et des besoins précis des beans à créer. Une des limites des annotations est qu'elles ne permettent pas de définir plusieurs beans Spring différents pour une seule classe Java ; une autre est que le code Java doit être recompilé pour que les changements de configuration soient pris en compte.
Certaines personnes utilisent ce dernier argument de reconfiguration sans compilation pour justifier l'utilisation du XML plutôt que les annotations. Dans certains cas l'argument est valable, mais bien souvent le fichier XML est stocké dans le classpath et n'est pas modifiable aisément quand l'application tourne dans un serveur productif (dans le cas des applications côté serveur) et une migration de l'applicatif complet empaqueté dans un war, ear ou jar est souvent nécessaire (d'ailleurs il serait assez risqué de ne déployer qu'un nouveau fichier XML à cause des possibles inconsistances).
Les arguments qu'on peut entendre sont parfois très subjectifs, par exemple « en XML on a toute la config à un seul endroit et c'est plus clair », « avec les annotations pas besoin de connaître le XML qui en plus n'est pas joli à écrire », « avec la config en XML les développeurs changent d'état mental et font plus attention à ce qu'ils écrivent » et un de mes préférés : « et si on doit retourner un jour à Java 1.4 ? ».
Ce débat n'est pas exclusif à Spring, mais existe aussi pour de nombreux autres frameworks qui ont petit à petit commencé à supporter les annotations. On voit maintenant de plus en plus souvent apparaître de nouveaux challengers : les DSL (Domain Specific Language) dont l'implémentation est rendue plus simple grâce à des langages comme Groovy, Ruby ou Scala et qui pourraient remplacer le XML pour la configuration des outils (Maven 3 par exemple). La discussion n'est pas prête de terminer.
IV-C-4. Bean Jardinier▲
Le bean Jardinier est un des plus simples, car sa classe a juste une propriété nom.
<!-- JARDINIER -->
<bean
id
=
"jardinier"
class
=
"org.yannart.springgarden.Jardinier"
p
:
nom
=
"Dupond"
/>
L'attribut id définit le nom qu'on va utiliser dans le contexte de Spring pour nommer le bean. L'attribut class est le nom complet (on inclut le package) de la classe que va instancier Spring. L'attribut p:nom permet de spécifier la valeur de la propriété nom. Cette notation est juste un raccourci pour ne pas avoir à utiliser l'élément property qui sera indispensable dans des cas plus complexes. La valeur va être injectée via le setter setNom().
La classe Java correspondante est la suivante :
/**
* Jardinier qui s'occupe du jardin.
*/
public
class
Jardinier implements
IJardinier {
/**
* Nom du jardinier.
*/
private
String nom;
@Override
public
String toString
(
) {
return
"Monsieur "
+
nom;
}
//GETTER ET SETTERS
...
}
IV-C-5. Bean Plante▲
Il y a différentes plantes dans le jardin. Comme leur fonctionnalité dans le cadre de cet exemple est toujours similaire, on définit une classe Plante unique.
Pour différencier les différentes variétés de plantes, on spécifie un attribut nomPlante. Dans le fichier applicationContext.xml on crée donc un bean pour chaque espèce de plante. Pour ne pas avoir à spécifier pour chaque bean la classe Java, on crée un bean parent qui sera étendu par chacun des beans fils. Comme il est possible de mixer la configuration par annotations et la configuration XML, on déclare le parent plante (identifiant par défaut) directement dans le code Java. Ensuite chaque plante est déclarée dans le XML.
Dans le fichier XML on a :
<!--PLANTES -->
<bean
id
=
"tomate"
parent
=
"plante"
scope
=
"prototype"
p
:
nomPlante
=
"Tomate"
/>
<bean
id
=
"patate"
parent
=
"plante"
scope
=
"prototype"
p
:
nomPlante
=
"Patate"
/>
<bean
id
=
"choux"
parent
=
"plante"
scope
=
"prototype"
p
:
nomPlante
=
"Choux"
/>
<bean
id
=
"pomme"
parent
=
"plante"
scope
=
"prototype"
p
:
nomPlante
=
"Pomme"
/>
<bean
id
=
"poire"
parent
=
"plante"
scope
=
"prototype"
p
:
nomPlante
=
"Poire"
/>
<bean
id
=
"poireMalade"
parent
=
"plante"
scope
=
"prototype"
p
:
nomPlante
=
"PoireMalade"
/>
<bean
id
=
"patateMalade"
parent
=
"plante"
scope
=
"prototype"
p
:
nomPlante
=
"PatateMalade"
/>
L'attribut scope avec la valeur prototype implémente le patron de conception du même nom et permet, lorsque l'on injecte ce bean dans un autre, de créer à chaque fois une instance d'objet différente. On verra par exemple que certaines de ces plantes seront injectées plusieurs fois dans un bean parcelle et à chaque fois l'instance sera nouvelle. Par défaut, les beans sont singletons et dans ce cas s'ils sont injectés, l'instance Java est toujours la même.
La classe Plante a la forme suivante :
import
org.springframework.stereotype.Component;
/**
* Classe abstraite que declare les methodes communes a toutes les plantes
*/
@Component
public
class
Plante implements
IPlante {
/**
* Nom de la plante.
*/
private
String nomPlante;
/**
* Quantité d'eau reçue.
*/
private
int
eau;
/**
* Quantité d'engrais reçu.
*/
private
int
engrais;
@Override
public
void
nourriEau
(
final
int
quantite) {
System.out.println
(
" - arrose la plante "
+
nomPlante +
" avec "
+
quantite +
"ml d'eau"
);
eau +=
quantite;
System.out.println
(
" --> "
+
nomPlante +
" a maintenant "
+
eau
+
"ml d'eau"
);
}
@Override
public
void
nourriEngrais
(
final
int
quantite) {
System.out.println
(
" - met "
+
quantite +
"g d'engrais a la plante "
+
nomPlante);
engrais +=
quantite;
System.out.println
(
" --> "
+
nomPlante +
" a maintenant "
+
engrais
+
"g d'engrais"
);
}
@Override
public
String toString
(
) {
return
nomPlante;
}
//GETTER ET SETTERS
...
}
IV-C-6. Beans Potager et Verger▲
Les beans potager et verger sont tous les deux des parcelles du jardin. Dans le fichier applicationContext.xml on crée donc un bean pour chaque parcelle du jardin.
Dans le fichier XML on a :
<!-- POTAGER -->
<bean
id
=
"potager"
class
=
"org.yannart.springgarden.Parcelle"
init-method
=
"melanger"
>
<property
name
=
"nom"
value
=
"Potager"
/>
<property
name
=
"plantes"
>
<list>
<ref
bean
=
"tomate"
/>
<ref
bean
=
"patate"
/>
<ref
bean
=
"patateMalade"
/>
<ref
bean
=
"choux"
/>
<ref
bean
=
"choux"
/>
</list>
</property>
</bean>
<!-- VERGER -->
<bean
id
=
"verger"
class
=
"org.yannart.springgarden.Parcelle"
init-method
=
"melanger"
>
<property
name
=
"nom"
value
=
"Verger"
/>
<property
name
=
"plantes"
>
<list>
<ref
bean
=
"pomme"
/>
<ref
bean
=
"poireMalade"
/>
<ref
bean
=
"poire"
/>
</list>
</property>
</bean>
Dans ces beans la balise list est utilisée pour injecter une liste de valeurs plutôt qu'une valeur unique. Entre les balises list, on spécifie les éléments contenus dans la liste. La balise ref permet de faire référence à des objets gérés par Spring. L'attribut bean indique l'id du bean qu'on référencie.
L'attribut init-method permet de définir une méthode du bean qui s'exécute immédiatement après avoir créé l'objet et lui avoir injecté les instances requises. Dans ce cas précis, init-method=« melanger » invoquera la méthode melanger(). Cette méthode sert uniquement à réordonner aléatoirement la liste des plantes de la parcelle.
La classe Parcelle est la suivante :
/**
* Une parcelle du jardin contient des plantes.
*/
public
class
Parcelle implements
IParcelle {
/**
* Plantes de la zone
*/
private
List<
IPlante>
plantes;
/**
* Nom de la zone
*/
private
String nom;
public
void
arroser
(
final
int
quantite) {
System.out.println
(
toString
(
) +
":"
);
for
(
IPlante plante : plantes) {
plante.nourriEau
(
quantite);
}
}
public
void
mettreEngrais
(
final
int
quantite) {
System.out.println
(
toString
(
) +
":"
);
for
(
IPlante plante : plantes) {
plante.nourriEngrais
(
quantite);
}
}
public
void
listerPlantes
(
) {
System.out.println
(
toString
(
) +
":"
);
for
(
IPlante plante : plantes) {
System.out.println
(
" "
+
plante.getDetail
(
));
}
}
public
void
melanger
(
) {
Collections.shuffle
(
plantes);
}
@Override
public
String toString
(
) {
return
nom;
}
//GETTERS ET SETTERS
...
IV-C-7. Bean Jardin▲
Le jardin a un potager, un verger et un jardinier.
Dans le fichier XML on a :
<!-- JARDIN -->
<bean
id
=
"jardin"
class
=
"org.yannart.springgarden.Jardin"
init-method
=
"jardiner"
>
<property
name
=
"jardinier"
ref
=
"jardinier"
/>
<property
name
=
"parcelles"
>
<list>
<ref
bean
=
"potager"
/>
<ref
bean
=
"verger"
/>
</list>
</property>
<!-- Les doses sont aléatoires -->
<property
name
=
"doseEau"
value
=
"#{ T(java.lang.Math).random() * 100 }"
/>
<property
name
=
"doseEngrais"
value
=
"#{ T(java.lang.Math).random() * 50 }"
/>
</bean>
Une référence au bean jardinier est injectée ainsi qu'une liste de parcelles. On définit aussi aléatoirement les valeurs des propriétés doseEau et doseEngrais en utilisant SpEL (Spring Expression Language) qui est une des nouveautés de Spring 3.
La classe Jardin est la suivante :
import
java.util.List;
/**
* Jardin qui contient differentes zones.
*/
public
class
Jardin implements
IJardin {
/**
* Parcelles du jardin.
*/
List<
IParcelle>
parcelles;
/**
* Jardinier du jardin.
*/
IJardinier jardinier;
/**
* Dose d'engrais par plante.
*/
int
doseEngrais;
/**
* Dose d'eau par plante.
*/
int
doseEau;
public
void
jardiner
(
){
// Montre le contenu du jardin
System.out.println
(
"
\n
JARDIN DE DEPART"
);
this
.listerParcelles
(
);
// Arrose tout le jardin
System.out.println
(
"
\n
ON ARROSE LE JARDIN"
);
this
.arroser
(
);
// Met de l'engrais a tout le jardin
System.out.println
(
"
\n
ON MET DE L'ENGRAIS DANS LE JARDIN"
);
this
.mettreEngrais
(
);
// Montre le contenu du jardin
System.out.println
(
"
\n
JARDIN A LA FIN"
);
this
.listerParcelles
(
);
}
public
void
arroser
(
) {
System.out.println
(
jardinier +
" arrose le Jardin"
);
for
(
IParcelle parcelle : parcelles) {
parcelle.arroser
(
doseEau);
}
}
public
void
mettreEngrais
(
) {
System.out.println
(
jardinier +
" met de l'engrais"
);
for
(
IParcelle parcelle : parcelles) {
parcelle.mettreEngrais
(
doseEngrais);
}
}
public
void
listerParcelles
(
) {
System.out.println
(
"Le Jardin de "
+
jardinier +
" contient:"
);
for
(
IParcelle parcelle : parcelles) {
parcelle.listerPlantes
(
);
}
}
//GETTERS ET SETTERS
}
IV-C-8. Bean MauvaiseHerbe▲
La mauvaise herbe est aussi une plante et n'a d'ailleurs rien de spécial. On montre juste des messages différents lors de l'invocation de ses méthodes. Le bean est configuré avec une annotation.
La classe Java :
import
org.springframework.stereotype.Component;
/**
* Mauvaise herbe.
*/
@Component
public
class
MauvaiseHerbe extends
Plante {
/**
* Construit une plante de mauvaise herbe.
*/
public
MauvaiseHerbe
(
) {
super
.setNomPlante
(
"MauvaiseHerbe"
);
}
@Override
public
void
nourriEau
(
int
quantite) {
System.out.println
(
" ** La mauvaise herbe vole l'eau !! **"
);
super
.nourriEau
(
quantite);
}
@Override
public
void
nourriEngrais
(
int
quantite) {
System.out.println
(
" ** La mauvaise herbe vole l'engrais !! **"
);
super
.nourriEngrais
(
quantite);
}
}
IV-C-9. Ajout d'AOP▲
Dans la plateforme Java l'AOP peut être implantée avec un tisseur d'aspect qui s'exécute à la compilation ou à l'exécution. AspectJ est sans doute le tisseur d'aspect le plus utilisé et sa version runtime s'intègre parfaitement avec Spring.
Une fois encore il est possible de faire de la configuration en XML ou avec des annotations. On choisit les annotations qui dans ce cas conviennent très bien.
Dans cet exemple, il s'agit de simuler que la mauvaise herbe reçoit l'eau et l'engrais au lieu d'une plante malade. Donc quand les méthodes nourriEau et nourriEngrais d'une plante malade s'invoquent, ce sont en réalité les méthodes de la mauvaise herbe que vont s'exécuter.
On déclare une classe qui définira l'aspect :
import
java.lang.reflect.InvocationTargetException;
import
java.lang.reflect.Method;
import
javax.annotation.Resource;
import
org.aspectj.lang.ProceedingJoinPoint;
import
org.aspectj.lang.annotation.Around;
import
org.aspectj.lang.annotation.Aspect;
import
org.aspectj.lang.reflect.MethodSignature;
import
org.springframework.stereotype.Component;
/**
* Cette classe implémente tout le mal de la mauvaise herbe ;-).
*/
@Aspect
@Component
public
class
MauvaiseHerbeAspect {
/**
* Mauvaise herbe qui vole les nutriments.
*/
@Resource
IPlante mauvaiseHerbe;
/**
* Advice associé à l'exécution d'une méthode avec le préfixe "nourri" d'un
* bean avec le suffixe "Malade".
*
*
@param
joinPoint
* référence au point de jonction de l'aspect.
*
@throws
IllegalAccessException
*
@throws
InvocationTargetException
*/
@Around
(
"execution(* nourri*(..)) && bean(*Malade)"
)
public
void
mauvaiseHerbeAdvice
(
final
ProceedingJoinPoint joinPoint)
throws
IllegalAccessException, InvocationTargetException {
// On sait que l'objet destin de l'appel est une Plante
IPlante plante =
(
IPlante) joinPoint.getTarget
(
);
// On sait que le JoinPoint est une méthode
Method method =
((
MethodSignature) joinPoint.getSignature
(
))
.getMethod
(
);
System.out.println
(
" * mauvaise herbe sent la methode "
+
method.getName
(
) +
" sur "
+
plante.getNomPlante
(
));
// Ce qu'allait recevoir la plante est reçu par la mauvaise herbe
method.invoke
(
mauvaiseHerbe, joinPoint.getArgs
(
));
}
}
Le code à exécuter est celui de la méthode mauvaiseHerbeAdvice annotée avec l'annotation @Around. Ce type d'advice fait une interception autour de la méthode qui allait être appelée sur l'objet cible, et empêche son exécution. Le flux peut aussi être intercepté avant ou après l'invocation du point de jonction (avec les annotations @Before et @After).
Un point de jonction est trouvé quand l'invocation d'une méthode correspond à un point de coupe.
Le point de coupe est défini avec l'expression execution(* nourri*(..)) && bean(*Malade) qui décrit que l'advice sera exécuté quand une méthode qui retourne n'importe quel type de valeur ayant comme préfixe nourri est invoquée sur un bean avec un id ayant comme suffixe le mot Malade.
Il faut préciser que la notation bean() dans l'expression du point de coupe est propre de Spring et ne fonctionnera pas en dehors de son contexte.
IV-D. Fichier de configuration Maven▲
Le fichier de configuration de Maven 2, nommé pom.xml se trouve à la racine du projet. Il déclare les propriétés du projet ainsi que ses dépendances.
<project
xmlns
=
"http://maven.apache.org/POM/4.0.0"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi
:
schemaLocation
=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
org.yannart</groupId>
<artifactId>
springgarden</artifactId>
<version>
0.0.1-SNAPSHOT</version>
<name>
Spring Garden</name>
<build>
<plugins>
<!-- Utilisation de Java 6 -->
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-compiler-plugin</artifactId>
<configuration>
<source>
1.6</source>
<target>
1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>
org.springframework</groupId>
<artifactId>
spring-core</artifactId>
<version>
3.0.0.RC2</version>
</dependency>
<dependency>
<groupId>
org.springframework</groupId>
<artifactId>
spring-context</artifactId>
<version>
3.0.0.RC2</version>
</dependency>
<dependency>
<groupId>
org.aspectj</groupId>
<artifactId>
aspectjrt</artifactId>
<version>
1.6.6</version>
</dependency>
<dependency>
<groupId>
org.aspectj</groupId>
<artifactId>
aspectjweaver</artifactId>
<version>
1.6.6</version>
</dependency>
</dependencies>
<!--
Repository pour Spring 3 (tant que la version finale n'est pas sortie)
-->
<repositories>
<repository>
<id>
springsource maven repo</id>
<url>
http://maven.springframework.org/milestone</url>
</repository>
</repositories>
</project>
V. Exécution du projet▲
La compilation et l'exécution de l'application peuvent se faire avec une seule commande Maven :
mvn compile exec:java -Dexec.mainClass="org.yannart.springgarden.SpringGardenApplication"
L'application s'exécute et vous aurez quelque chose semblable à ça :
JARDIN DE DEPART
Le Jardin de Monsieur Dupond contient:
Potager:
- Patate avec 0ml d'eau et 0g d'engrais
- Choux avec 0ml d'eau et 0g d'engrais
- PatateMalade avec 0ml d'eau et 0g d'engrais
- Choux avec 0ml d'eau et 0g d'engrais
- Tomate avec 0ml d'eau et 0g d'engrais
Verger:
- PoireMalade avec 0ml d'eau et 0g d'engrais
- Poire avec 0ml d'eau et 0g d'engrais
- Pomme avec 0ml d'eau et 0g d'engrais
ON ARROSE LE JARDIN
Monsieur Dupond arrose le Jardin
Potager:
- arrose la plante Patate avec 23ml d'eau
--> Patate a maintenant 23ml d'eau
- arrose la plante Choux avec 23ml d'eau
--> Choux a maintenant 23ml d'eau
* mauvaise herbe sent la methode nourriEau sur PatateMalade
** La mauvaise herbe vole l'eau !! **
- arrose la plante MauvaiseHerbe avec 23ml d'eau
--> MauvaiseHerbe a maintenant 23ml d'eau
- arrose la plante Choux avec 23ml d'eau
--> Choux a maintenant 23ml d'eau
- arrose la plante Tomate avec 23ml d'eau
--> Tomate a maintenant 23ml d'eau
Verger:
* mauvaise herbe sent la methode nourriEau sur PoireMalade
** La mauvaise herbe vole l'eau !! **
- arrose la plante MauvaiseHerbe avec 23ml d'eau
--> MauvaiseHerbe a maintenant 46ml d'eau
- arrose la plante Poire avec 23ml d'eau
--> Poire a maintenant 23ml d'eau
- arrose la plante Pomme avec 23ml d'eau
--> Pomme a maintenant 23ml d'eau
ON MET DE L'ENGRAIS DANS LE JARDIN
Monsieur Dupond met de l'engrais
Potager:
- met 45g d'engrais a la plante Patate
--> Patate a maintenant 45g d'engrais
- met 45g d'engrais a la plante Choux
--> Choux a maintenant 45g d'engrais
* mauvaise herbe sent la methode nourriEngrais sur PatateMalade
** La mauvaise herbe vole l'engrais !! **
- met 45g d'engrais a la plante MauvaiseHerbe
--> MauvaiseHerbe a maintenant 45g d'engrais
- met 45g d'engrais a la plante Choux
--> Choux a maintenant 45g d'engrais
- met 45g d'engrais a la plante Tomate
--> Tomate a maintenant 45g d'engrais
Verger:
* mauvaise herbe sent la methode nourriEngrais sur PoireMalade
** La mauvaise herbe vole l'engrais !! **
- met 45g d'engrais a la plante MauvaiseHerbe
--> MauvaiseHerbe a maintenant 90g d'engrais
- met 45g d'engrais a la plante Poire
--> Poire a maintenant 45g d'engrais
- met 45g d'engrais a la plante Pomme
--> Pomme a maintenant 45g d'engrais
JARDIN A LA FIN
Le Jardin de Monsieur Dupond contient:
Potager:
- Patate avec 23ml d'eau et 45g d'engrais
- Choux avec 23ml d'eau et 45g d'engrais
- PatateMalade avec 0ml d'eau et 0g d'engrais
- Choux avec 23ml d'eau et 45g d'engrais
- Tomate avec 23ml d'eau et 45g d'engrais
Verger:
- PoireMalade avec 0ml d'eau et 0g d'engrais
- Poire avec 23ml d'eau et 45g d'engrais
- Pomme avec 23ml d'eau et 45g d'engrais
On remarque que les plantes malades ne reçoivent jamais d'eau ni d'engrais, c'est la mauvaise herbe qui vole tout.
Juste pour le fun, essayez de changer la valeur nourri par nourriEngrais dans la classe MauvaiseHerbeAspect :
@Around
(
"execution(* nourriEngrais*(..)) && bean(*Malade)"
)
La mauvaise herbe n'a plus soif !
VI. Sources▲
Les sources sont fournis comme projet Maven 2 et projet Eclipse avec m2eclipse. Vous n'avez qu'à décompresser l'archive et lancer l'exécution. Accès FTP (HTTP Secours)
Vous pouvez télécharger les sources avec Subversion depuis l'url http://springgarden.googlecode.com/svn/trunk/
VII. Conclusion▲
Nous avons rapidement fait une petite application qui utilise quelques fonctionnalités IoC et AOP de Spring. L'exemple est assez simple et j'espère qu'il a été parlant et facile à comprendre.
Spring est un framework énorme en fonctionnalités et utilisable dans de nombreuses situations. Alors pourquoi se priver d'apporter un peu de fraîcheur à vos développements ?
VIII. Remerciements▲
Merci à keulkeul et christopheJ pour leurs retours techniques, à ricky81 pour son aide à la publication, ainsi qu'à mlny84 pour sa relecture orthographique.