Spring AOP

Introduction

Spring est un framework open source J2EE pour les applications n-tiers, dont il facilite le développement et les tests. Il est considéré comme un conteneur dit « léger », c’est-à-dire une infrastructure similaire à un serveur d’application J2EE. Il prend donc en charge la création d’objets et la mise en relation d’objets par l’intermédiaire d’un fichier de configuration qui décrit les objets à fabriquer et les relations de dépendances entre ces objets.

Le gros avantage par rapport aux serveurs d’application est qu’avec Spring, vos classes n’ont pas besoin d’implémenter une quelconque interface pour être prises en charge par le framework (au contraire des serveurs d’applications J2EE et des EJBs). C’est en ce sens que Spring est qualifié de conteneur « léger ».
Spring s’appuie principalement sur l’intégration de trois concepts clés :

  1. l’inversion de contrôle ou injection de dépendance (IoC).
  2. la programmation orientée aspect (AOP).
  3. une couche d’abstraction.

Ce framework, grâce à sa couche d’abstraction, ne concurrence pas d’autres frameworks dans une couche spécifique d’un modèle architectural MVC mais s’avère un framework multi-couches pouvant s’insérer au niveau de toutes les couches.

Nous allons aborder dans ce deuxième tutoriel le second concept : AoP (Aspect-Oriented Programming).

Les spécifications d’une application sont réparties dans la couche métier. Dans la pratique, les modules d’une applications s’entrecroisent. Ainsi, une couche logicielle initialement dédiée à gérer la logique métier applicative, va se retrouver dépendante de modules gérant les aspects transactionnels, journalisation, etc… La programmation orientée aspect va permettre d’extraire les dépendances entre ces modules.

A quoi sert le concept de la programmation orientée aspect dans Spring? Il permet d’ajouter, par configuration, du “comportement” à une méthode de classe sans modifier son code.

Site Officiel

Vous trouverez le site officiel de Spring à l’adresse suivante : http://www.springsource.org/

Wikipédia

Je vous invite à consulter les articles français et anglais de la célèbre encyclopédie Wikipédia à propos du Framework Spring.

Tutoriel

Avant d’entamer ce tutoriel, vérifiez que vous avez bien les pré requis :

  • le JDK doit être installé sur votre machine.
  • Eclipse doit être installé sur votre machine. Si vous ne l’avez pas installé, référez vous à ce tutoriel.
      Dans ce tutoriel, nous allons montrer comment tracer les appels d’une méthode dans une application basée sur Spring. Pendant le développement ou la maintenance d’une application, le développeur n’a pas forcément envie de mettre dans son code des log.debug() partout pour mieux comprendre le cheminement de l’application ou tracer un bug. Avec Spring, il est possible de tracer les appels aux méthodes objets pendant l’exécution ET SANS modifier le code de l’application. Nous allons voir avec cet exemple l’intérêt de Spring AOP.

Création du projet dans Eclipse

      • Lancer Eclipse.
      • On crée un nouveau projet. Pour cela on ouvre le menu File → New → Java Project.
      • La fenêtre de wizard apparaît alors. Nous allons simplement indiquer ici le nom du projet dans le champ Project name : sf-sample-spring-aop
      • On peut ensuite cliquer sur Finish. Le projet apparait alors dans l’onglet Project Explorer qui répertorie les différents projets du workspace.

Ajout des librairies

      • Il faut télécharger le Framework Spring à l’adresse suivante : Spring Framework 2.5.6
      • On va ensuite extraire le contenu de l’archive zip dans notre projet. Pour ce faire, on ouvre un explorateur de fichiers, pour accéder au workspace (sous Ubuntu il sera situé par défaut dans /home/user/workspace), on va dans le répertoire du projet, dans lequel on crée un nouveau répertoire lib qui contiendra les librairies. On extrait ensuite l’archive dedans.

Si le répertoire lib n’apparaît pas dans votre projet sous Eclipse, sélectionnez votre projet et appuyez sur F5 pour rafraîchir le projet.

      • Il faut ensuite spécifier à Eclipse dans notre projet qu’on a ajouté une librairie pour pouvoir l’utiliser. On fait un clic droit sur le projet dans le package explorer qui ouvre un menu contextuel : on ouvre Build Path → Configure Build Path. On clic sur l’onglet Libraries, puis sur le bouton add JARS…. Il suffit ensuite d’ajouter les bibliothèques suivantes spring.jar, aspectjrt.jar, aspectjweaver.jar, cglib-nodep-2.1_3.jar, commons-logging.jar, et junit-4.4.jar du Framework Spring situées dans les sous répertoires de lib du projet sf-sample-spring-aop. On valide les modifications sur le projet en cliquant sur OK. Nous sommes maintenant prêts à débuter le code!

Implémentation de la première classe

Nous allons désormais créer une classe java tout ce qu’il y a de plus simple. C’est un exemple volontairement simpliste afin de se concentrer sur la programmation orientée aspect de Spring. Vous allez voir c’est un jeu d’enfant ! Nous allons tout d’abord créer un service qui permettra d’envoyer un message d’un expéditeur sur la sortie par défaut du système, à savoir la console.

      • On se place sur le répertoire src du projet sf-sample-spring-aop, puis on fait un clic droit, New → Class.
      • La fenêtre de wizard New Java Class est normalement apparue. On indique dans le champ Package com.scub.foundation.samples.spring.aop.service et on entre dans Name le nom MessageService puis on clique sur Finish.
      • Voici le code de la classe MessageService commentée ci-dessous :
package com.scub.foundation.tutorial.spring.aop.service;
 
/**
 * Service permettant l'envoi de messages d'un expediteur sur la console.
 * @author Scub-Foundation
 */
public class MessageService {
 
	/**
	 * Méthode qui formate et affiche dans la console un message.
	 * @param expediteur l'expéditeur du message
	 * @param msg le contenu du message
	 * @return le message fabriqué
	 */
	public String envoiMsg(String expediteur, String msg){
		String message ="-> " + expediteur + " dit : " + msg + "\n"; 
		System.out.print(message);
		return message;
	}
 
	/**
	 * Compare deux messages
	 * @param msg1 le premier message
	 * @param msg2 le second message
	 * @return true si les messages sont identiques, false sinon
	 */
	public boolean cmpMsg(String msg1, String msg2){
		return msg1.equals(msg2);
	}
}

Création du test unitaire

Nous allons ensuite créer un test unitaire pour appeler et tester notre petit service :

      • Placez vous sur le dossier src du projet sf-samples-spring-aop, clic droit dessus New → JUnit Test Case
      • La fenêtre de wizard New JUnit Test Case est normalement apparue. On indique dans le champ Package com.scub.foundation.samples.spring.aop.test et on entre dans Name le nom MessageServiceTest puis on coche la case setUp() et on clique enfin sur Finish.
      • La classe a du être créé dans le répertoire src. On y ajoute le code suivant :
package com.scub.foundation.tutorial.spring.aop.test;
 
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertFalse;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
import com.scub.foundation.tutorial.spring.aop.service.MessageService;
 
/**
 * Test unitaire du service de message.
 * @author Scub-Foundation
 */
public class MessageServiceTest {
 
	private ApplicationContext context;
 
	/**
	 * Méthode appelée à l'initialisation de la classe de test Unitaire.
	 * @throws Exception
	 */
	@Before
	public void setUp() throws Exception {
		context = new ClassPathXmlApplicationContext( new String[] {"spring.context.xml"} );
	}
 
	/**
	 * Test du service envoiMsg
	 */
	@Test
	public void testEnvoiMsg(){
		// On récupère le service de message instancié avec spring 
		MessageService service = (MessageService) context.getBean("MessageService");
 
		String expediteur = "Nico";
		String msg1 = "Bienvenu dans le tutoriel de Spring AOP!";
 
		// On envoie un premier message
		String msgEnvoye1 = service.envoiMsg(expediteur, msg1);
 
		// On vérifie qu'il a bien été envoyé
		assertNotNull("Le message "+msg1+" n'a pas été envoyé", msgEnvoye1);
 
		String msg2 = "Félicitation, vous avez achevé le tutoriel!";
 
		// On envoie un second message
		String msgEnvoye2 = service.envoiMsg(expediteur, msg2);
 
		// On vérifie qu'il a bien été envoyé
		assertNotNull("Le message "+msg2+" n'a pas été envoyé", msgEnvoye2);
 
		// On compare les deux messages envoyés
		boolean comparaisonMsg = service.cmpMsg(msgEnvoye1, msgEnvoye2);
 
		assertFalse("Les messages envoyés sont identiques", comparaisonMsg);
	}
}

Implémentation de la classe de génération des logs

Nous allons maintenant implémenter la classe MessageLogger qui contiendra deux méthodes :

      • logMethodEntry() qui affiche dans la console le nom de la méthode et ses paramètres
      • logMethodExit() qui affiche dans la console le nom de la méthode et ce qu’elle renvoie

Pour la créer :

      • On se place sur le répertoire src du projet sf-sample-spring-aop, puis on fait un clic droit, New → Class.
      • La fenêtre de wizard New Java Class est normalement apparue. On indique dans le champ Package com.scub.foundation.samples.spring.aop.service et on entre dans Name le nom MessageLogger puis on clique sur Finish.
      • Voici le code de la classe MessageLogger commentée ci-dessous :
package com.scub.foundation.tutorial.spring.aop.service;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;
 
/**
 * Classe génératrice de logs.
 * @author Scub-Foundation
 */
public class MessageLogger {
 
	/**
	 * Cette méthode est appelée à chaque fois (et avant) qu'une méthode est
	 * interceptée
	 * @param joinPoint 
	 */
	public void logMethodEntry(JoinPoint joinPoint) {
 
		Object[] args = joinPoint.getArgs();
 
		// Nom de la méthode interceptée
		String name = joinPoint.getSignature().toLongString();
		StringBuffer sb = new StringBuffer(name + " appelé avec en paramètre : [");
 
		// Liste des valeurs des arguments reçus par la méthode
		for (int i = 0; i < args.length; i++) {
			Object o = args[i];
			sb.append("'" + o + "'");
			sb.append((i == args.length - 1) ? "" : ", ");
		}
		sb.append("]");
 
		System.out.println(sb);
	}
 
	/**
	 * Cette méthode est appelée à chaque fois (et après) qu'une méthode est
	 * interceptée Elle reçoit en argument 'result' qui est le retour de la
	 * méthode interceptée
	 * @param staticPart 
	 * @param result 
	 */
	public void logMethodExit(StaticPart staticPart, Object result) {
 
		// Nom de la méthode interceptée
		String name = staticPart.getSignature().toLongString();
 
		System.out.println(name + " retourne : [" + result + "]");
	}
 
}

Configuration de Spring

Il reste encore à configurer Spring. Nous allons utiliser un fichier XML comme dans les tutoriels précédents. Pour le créer il suffit de faire :

      • Clic droit sur src dans le package explorer
      • New → Other → XML avec comme nom spring.context.xml
<?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"
	xsi:schemaLocation="
			http://www.springframework.org/schema/beans http://www.springframework.org/schema/
			beans/spring-beans-2.5.xsd
			http://www.springframework.org/schema/aop http://www.springframework.org/schema/
			aop/spring-aop-2.5.xsd">
 
	<bean id="MessageLogger"
		class="com.scub.foundation.tutorial.spring.aop.service.MessageLogger" />
 
	<bean id="MessageService"
		class="com.scub.foundation.tutorial.spring.aop.service.MessageService" />
 
	<!-- Début de la configuration AOP -->
	<aop:config>
		<aop:pointcut id="servicePointcut"
			expression="execution(* com.scub.foundation.tutorial.spring.aop.service.*.*(..))" />
 
		<aop:aspect id="loggingAspect" ref="MessageLogger">
			<aop:before method="logMethodEntry" pointcut-ref="servicePointcut" />
			<aop:after-returning method="logMethodExit"
				returning="result" pointcut-ref="servicePointcut" />
		</aop:aspect>
	</aop:config>
	<!-- Fin de la configuration AOP -->
</beans>

Dans le fichier de configuration :

      • On déclare les beans MessageService et MessageLogger.
      • Et on spécifie la configuration AOP.

Avant de détailler la suite de la configuration , il est nécessaire d’expliquer quelques termes introduits par le paradigme de programmation et les nouveaux concepts de l’AOP :

      • aspect : un module définissant des greffons et leurs points d’activation,
      • advice (greffon) : un programme qui sera activé à un certain point d’exécution du système, précisé par un point de jonction,
      • pointcut (point d’action, de coupure, de greffe) : endroit du logiciel où est inséré un greffon par le tisseur d’aspect,
      • joinpoint (point de jonction, d’exécution) : endroit spécifique dans le flot d’exécution du système, où il est valide d’insérer un greffon. Pour clarifier le propos, il n’est pas possible, par exemple, d’insérer un greffon au milieu du code d’une fonction avec Spring AOP. Par contre on pourra le faire avant, autour de, à la place ou après l’appel de la fonction.

Dans le fichier XML au niveau de la configuration AOP, on déclare :

      • <aop:pointcut id=“servicePointcut” expression=“execution(* com.scub.foundation.samples.spring.aop.service.*.*(..))” />: permet de définir des points d’interception sur les objets suivants :
          • com.scub.foundation.samples.spring.aop.service.*.* signifie que toutes les méthodes des objets qui sont dans le package com.scub.foundation.samples.spring.aop.service seront interceptées
      • <aop:aspect id=“loggingAspect” ref=“MessageLogger”> : les appels aux méthodes seront renvoyés vers le bean Spring MessageLogger défini auparavant.
      • <aop:before method=“logMethodEntry” pointcut-ref=“servicePointcut” /> : avant l’exécution de n’importe quelle méthode d’une classe du package com.scub.foundation.samples.spring.aop.service, la méthode logMethodEntry() est appelée
      • <aop:after-returning method=“logMethodExit” returning=“result” pointcut-ref=“servicePointcut” /> : après l’exécution de n’importe quelle méthode d’une classe du package com.scub.foundation.samples.spring.aop.service, la méthode logMethodExit() est appelée et le résultat retourné lui sera passé en argument.

Exécution du Test Unitaire

On peut alors exécuter le test Unitaire qui produira dans la console le résultat suivant :

On constate bien que pour chaque appel de méthode des classes du package com.scub.foundation.samples.spring.aop.service dans notre test unitaire, des logs avant et après appel sont produits dans la console. Et tout cela sans avoir apporté la moindre modification à notre classe de service MessageService initiale.

Pourquoi utiliser Spring AOP?

La Programmation Orientée Aspect (AOP) complète la Programmation Orientée Objet (OOP) en fournissant une autre manière de penser sur la structure d’un programme. L’unité de modularité en OOP est la classe, alors que c’est l’aspect pour l’AOP. Les aspects permettent la modularisation des préoccupations telle que la gestion des transactions qui s’entrecroisent à travers des types et des objets multiples.
L’AOP est un composant clé du Framework Spring. le module AOP de Spring complète Spring IoC afin de fournir une solution pratique middleware.

Découplage

Grâce à l’AOP, le couplage entre les modules gérant des aspects techniques peut être réduit de façon très importante, en utilisant ce principe, ce qui présente de nombreux avantages :

      • Maintenance accrue : les modules techniques, sous forme d’aspect, peuvent être maintenus plus facilement du fait de son détachement de son utilisation,
      • Meilleure réutilisation : tout module peut être réutilisé sans se préoccuper de son environnement et indépendamment du métier ou du domaine d’application. Etant donné que chaque module implémentent une fonctionnalité technique précise, on n’a pas besoin de se préoccuper des évolutions futures : de nouvelles fonctionnalités pourront être implémentées dans de nouveaux modules qui interagiront avec le système au travers des aspects.
      • Gain de productivité : le programmeur ne se préoccupe que de l’aspect de l’application qui le concerne, ce qui simplifie son travail, et permet d’augmenter la parallélisation du développement.
      • Amélioration de la qualité du code : La simplification du code qu’entraîne la programmation par aspect permet de le rendre plus lisible et donc de meilleure qualité.

Code non intrusif

Nous avons vu dans ce tutoriel que Spring AOP permet par exemple de mettre en place des logs pour un service, sans devoir modifier le code du service. On peut utiliser le terme de “code non intrusif” pour définir ce concept. C’est un avantage non négligeable qui facilite grandement le travail des développeurs pendant le déboguage et la maintenance.

Télécharger les sources

Les sources du projet Eclipse pour ce tutoriel sont téléchargeables sur Source Forge à l’adresse suivante.