Spring AOP & Interceptors

Si vous lisez cet article c’est que vous voulez en savoir plus sur la création d’intercepteurs avec Spring AOP.

Mais au fait, un intercepteur, qu’est ce que c’est et à quoi ça sert ?

Déjà, petit retour sur la programmation par aspect (AOP). L’AOP sert à découper les problématiques métier et technique. De cette manière il est possible de se concentrer sur la logique métier d’un coté et de développer les aspects techniques dans d’autres modules, rendant le code plus modulaire, plus facile à maintenir et à faire évoluer.

Un intercepteur, dans le contexte de cet article, est un greffon qui permet d’exécuter une méthode autour d’une autre… Plus clairement, il va ‘intercepter’ l’appel à la méthode cible afin d’effectuer des traitement en amont, en aval ou autour de celle-ci. De ce fait il est possible d’effectuer des vérifications avant d’appeler une méthode (pour vérifier les droits de l’appelant ou initialiser un contexte spécifique par exemple), après un appel (pour clôturer une session ou effectuer une transformation sur l’objet retourné), ou bien autour de l’appel. Cette dernière solution est la plus flexible, donnant la possibilité de faire appel ou non à la méthode cible ainsi que de maîtriser totalement le contexte d’appel.

Ça a l’air bien, comment on s’en sert ?

À l’aide de Spring Framework, la création d’intercepteurs est assez simple, il est possible de le faire à l’aide d’annotations ou en xml. Nous allons utiliser la méthode xml dans cet exemple.

Nous partons ici du principe que votre application possède déjà un contexte Spring chargé à partir de fichiers xml.

Avant toute chose il faut que votre fichier xml de configuration contienne les éléments suivants dans la balise beans afin de pouvoir comprendre les balises relatives aux AOP :

xmlns:aop="http://www.springframework.org/schema/aop"

xsi:schemaLocation="http://www.springframework.org/schema/aop   http://www.springframework.org/schema/aop/spring-aop.xsd"

Votre fichier xml resemblera alors à quelque chose comme ça :

<?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:jaxrs="http://cxf.apache.org/jaxrs"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop   http://www.springframework.org/schema/aop/spring-aop.xsd
        http://cxf.apache.org/jaxrs                 http://cxf.apache.org/schemas/jaxrs.xsd">

    	<!--	reste du fichier de configuration	-->

</beans>

Si cela vous parait complexe je vous invite à parcourir la documentation de Spring.

Tout d’abord, créons le bean de l’intercepteur.

<bean id="exampleInterceptor" class="tdl.company.product.package.ExampleInterceptor" autowire="byName"/>

Jusque là pas de soucis.

Ensuite nous allons configurer l’aop en lui même.
Pour cela, tout se passe à l’intérieur de la la balise <aop:config>.
La balise <aop:aspect> permet de configurer un aspect, ici, l’intercepteur nouvellement créé.
Il est nécessaire de fournir à l’aspect un point de coupe, ou pointcut, qui est une restriction de Spring AOP, et qui spécifie sur quelles méthodes de quelles classes l’aspect sera appelé.
Ici l’expression « bean(product*Rest) » cible toutes les méthodes des beans dont l’id est de la forme product*Rest tel que productUserRest ou productRightRest.
La balise suivante, ici <aop:around>, indique que l’intercepteur sera exécuté autour du pointcut référencé par « pointcut-ref ».
Il existe un grand nombre d’expressions de pointcut tel que :

  • expression( . . . ) qui permet de spécifier une méthode en particulier
  • within( com.xyz.* ) pour les méthodes d’un package
  • args( java.io.serializable ) pour les méthodes prenant un argument particulier

Vous pouvez trouver plus d’explications sur ces expressions ainsi que d’autres expressions dans la documentation de Spring.

Le paramètre « method » permet de spécifier quelle méthode de l’intercepteur sera exécutée.

<aop:config>
	<aop:aspect id="productRestAspect" ref="exampleInterceptor">
		<aop:pointcut expression="bean(product*Rest)" id="productRestPointcut" />
		<aop:around pointcut-ref="productRestPointcut" method="execute" />
	</aop:aspect> 
</aop:config>

Dans l’exemple suivant la méthode qui sera exécutée sera execute( ProceedingJoinPoint pjp )

La classe ProceedingJoinPoint contient la méthode appelée ainsi que les arguments qui lui sont passés, de ce fait il faudra, à l’intérieur de la méthode execute, faire un appel à pjp.proceed() si l’intercepteur est défini autour de point de coupe.
Si l’aspect est défini avant ou après le point de coupe, l’appel à la fonction sera réalisé avant ou après la méthode de l’intercepteur. Dans ce cas il ne faut pas appeler la méthode cible.

La méthode execute ressemblera donc à quelque chose comme :

public Object execute( ProceedingJoinPoint pjp ) throws Throwable
{
	// traitement avant appel

	Object response = pjp.proceed();

	// traitement apres appel

	return response;
}

Voila, vous avez créer un intercepteur.

Et si j’en veux plusieurs ?

Vous voulez définir plusieurs Intercepteurs sur les même méthodes?

Pas de soucis, cependant il va falloir définir dans quel ordre ces intercepteurs vont être appelés.
Vous pouvez alors faire implémenter à vos intercepteurs l’interface Ordered venant de Spring Framework et donner à vos classes une valeur qui permettra d’ordonner l’exécution des intercepteurs.

Plus la valeur donnée sera grande plus l’interception se fera proche de l’objet. À l’inverse, plus la valeur sera petite plus l’interception se fera loin de l’objet.
Un petit exemple étant plus parlant qu’un long discours, voici comment cela peut être représenté :

Nous avons deux intercepteurs ayant des valeurs d’ordre 10 et 100 que nous appellerons respectivement interceptor10 et interceptor100, une classe, Class, autour de laquelle sont définis ces intercepteurs et une fonction method() appartenant à Class.

Les greffons ressembleront à ceci :

package com.alinto.licence.vm.kernel.interceptors;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.core.Ordered;

public class Interceptor100 implements Ordered
{	
	private static final int	ASPECT_PRECEDENCE_ORDER	= 100;

	public Object execute( ProceedingJoinPoint pjp ) throws Throwable
	{
		// code à exécuter avant l'appel

		Object response = pjp.proceed();

		// code à exécuter après l'appel

		return response;
	}

	@Override
	public int getOrder()
	{
		return Interceptor100.ASPECT_PRECEDENCE_ORDER;
	}

}

Lors de l’appel à method(), l’intercepteur extérieur, interceptor10, interceptera l’appel. la méthode execute sera appelé à la place. Interceptor100 interceptera à son tour l’appel puis method() sera appelé. La valeur retournée par method() redescendra dans la liste d’appels en passant par Interceptor100 puis Interceptor10 et sera retournée à la méthode appelante originale.

Bien évidement les possibilités offertes par Spring AOP ne se limitent pas à cela, pour en savoir plus et/ou pour approfondir,vous pouvez allez faire un tour sur docs.spring.io.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *