Niveau : | Confirmé |
L'AOP permet de facilement mettre en place des fonctionnalités dans différents points d'une application. Ces fonctionnalités sont désignées sous le terme advice : elles sont exécutées lors d'événements nommés joinpoint (par exemple l'invocation d'une méthode ou d'un constructeur, ...).
Les endroits où les advices seront invoqués lorsque le joinpoint est réalisé sont définis grâce à des pointcuts.
Une opération de tissage est nécessaire pour permettre l'exécution des aspects au runtime : ce tissage peut être réalisé dynamiquement (grâce à un classloader ou la création de proxys) ou par compilation.
L'AOP est particulièrement intéressante pour mettre en oeuvre certaines fonctionnalités techniques transverses comme les transactions. C'est d'ailleurs grâce à l'AOP que les transactions sont gérées par Spring. La gestion des transactions devient alors déclarative et ne requiert plus de code supplémentaire utilisant une API dédiée.
L'AOP est un des mécanismes importants utilisés par Spring : il l'utilise lui-même pour mettre en oeuvre certaines fonctionnalités notamment les transactions, l'annotation @Configurable, ROO, ...
Ainsi, l'AOP peut être utilisée :
Spring met en oeuvre l'AOP de deux façons :
L'AOP peut être mise en oeuvre via Spring AOP ou AspectJ de plusieurs manières :
La mise en oeuvre peut donc se faire par déclaration dans le fichier de configuration ou par des annotations selon la solution de tissage utilisée. Toutes les combinaisons de syntaxe de déclaration avec la méthode de tissage ne sont pas possibles :
Syntaxe AspectJ |
Annotation style AspectJ |
XML dans la définition du context |
|
Tissage par Spring |
Non |
Oui |
Oui |
Tissage par AspectJ |
Oui |
Oui |
Non |
Spring AOP ne permet qu'un tissage au runtime qui va créer des proxys dynamiquement lors du chargement du contexte, selon la configuration indiquée.
Spring AOP ne propose pas un support des fonctionnalités de programmation orientée aspect aussi poussé que celui proposé par AspectJ. La mise en oeuvre de Spring AOP ne peut se faire que sous certaines conditions :
Ce chapitre contient plusieurs sections :
Spring AOP est un module du framework Spring qui permet une mise en oeuvre d'une partie des fonctionnalités de l'AOP. Il propose un tisseur d'aspects sous la forme de proxys qui sont créés dynamiquement au runtime.
Depuis la version 2.0, la définition d'un aspect avec Spring AOP peut se faire grâce à une déclaration dans le fichier de configuration du contexte ou grâce aux annotations d'AspectJ.
Durant l'injection des dépendances, le conteneur Spring va créer un proxy dynamique pour l'interface concernée et c'est ce proxy qui sera injecté. Ce proxy est en charge d'exécuter le code des greffons lors de l'invocation des méthodes concernées de l'interface. Un des avantages des aspects est qu'ils sont facilement activables/désactivables : les fonctionnalités qu'ils contiennent peuvent alors être activées ou non sans modifier les classes greffées.
Spring 2.0 facilite la configuration d'AOP en proposant un schéma et un espace de nommage associé dédiés.
Spring AOP utilise des proxys ce qui ne nécessite pas d'outils particulier comme c'est le cas avec AspectJ pour réaliser le tissage (classloader ou compilateur dédié). Spring permet une exécution des advices sur une instance précise alors qu'avec AspectJ l'advice sera exécuté pour toutes les instances puisque la définition est faite sur le type.
Le but de Spring AOP n'est pas de proposer un support complet des fonctionnalités de l'AOP mais de proposer la possibilité de mettre en oeuvre des fonctionnalités transverses qui s'intègrent dans le conteneur Spring. Ainsi, Spring AOP ne propose qu'un support des points de jonction de type exécution de méthodes. Pour la mise en oeuvre d'autres types de points de jonction, il faut utiliser une solution qui propose leur support comme AspectJ.
Spring AOP propose 5 types d'advices :
Il est recommandé d'utiliser l'advice le plus adapté au besoin plutôt que de tout faire avec un advice de type around : cette bonne pratique permet de simplifier le code et d'éviter des erreurs potentielles.
Les paramètres des advices sont fortement typés.
Sans utiliser les annotations AspectJ, il est possible de mettre en oeuvre Spring AOP en utilisant le fichier de configuration du contexte pour déclarer et configurer les aspects.
L'exemple de cette section va développer un service sur lequel l'invocation des méthodes va être tracée grâce à un aspect. Cet aspect trace les invocations des méthodes des services en affichant les paramètres d'invocation et la valeur de retour.
Les fonctionnalités du service sont définies par une interface.
Exemple : |
package com.jmdoudoux.test.spring.service;
import com.jmdoudoux.test.spring.entite.Personne;
public interface PersonneService {
void afficher();
void ajouter(Personne personne);
}
L'implémentation du service est volontairement basique.
Exemple : |
package com.jmdoudoux.test.spring.service;
import com.jmdoudoux.test.spring.entite.Personne;
public class PersonneServiceImpl implements PersonneService {
@Override
public void afficher() {
try {
Thread.sleep(250);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void ajouter(final Personne personne) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Les traitements de l'aspect vont simplement tracer l'invocation d'une méthode avec les paramètres utilisés, invoquer la méthode et tracer la fin de l'invocation.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
public class TraceInvocation {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
private int order;
public Object afficherTrace(final ProceedingJoinPoint joinpoint)
throws Throwable {
String nomMethode = joinpoint.getTarget().getClass().getSimpleName() + "."
+ joinpoint.getSignature().getName();
final Object[] args = joinpoint.getArgs();
final StringBuffer sb = new StringBuffer();
sb.append(joinpoint.getSignature().toString());
sb.append(" avec les parametres : (");
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
sb.append(")");
LOGGER.info("Debut methode : " + sb);
try {
Object obj = joinpoint.proceed();
} finally {
LOGGER.info("Fin methode : " + nomMethode + " retour=" + obj);
}
return obj;
}
}
Le code de l'aspect à exécuter doit être contenu dans une méthode qui attend en paramètre un objet de type ProceedingJoinPoint. La classe contenant la méthode doit être instanciable par le contexte.
La classe ProceedingJoinPoint d'AspectJ est utilisée pour obtenir des informations sur le point de jonction et invoquer les traitements qui lui sont associés en utilisant la méthode proceed().
La déclaration et la configuration des aspects se font dans le fichier de configuration.
L'espace de nommage aop permet la déclaration de la configuration de l'AOP notamment en proposant les tags pour configurer les aspects, les points de coupe et les advices.
L'aspect fait référence à un bean géré par le conteneur.
La définition du point de coupe utilise la syntaxe d'AspectJ.
L'advice est une association entre le point de coupe et la méthode de l'aspect à exécuter. Cinq types d'advices sont utilisables : before, after returning, after throwing, after et around.
Spring 2.0 permet la déclaration des aspects dans la configuration de son contexte qui utilise la syntaxe d'AspectJ pour les définitions des pointcuts. Dans ce cas, l'aspect n'a pas besoin d'être annoté : c'est un simple bean qui doit être déclaré dans la configuration.
Exemple : |
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.jmdoudoux.test.spring" />
<aop:config>
<aop:aspect id="traceInvocationAspect" ref="tracerInvocation">
<aop:pointcut id="traceInvocationPointcut"
expression="execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
<aop:around pointcut-ref="traceInvocationPointcut" method="afficherTrace" />
</aop:aspect>
</aop:config>
<bean id="tracerInvocation" class="com.jmdoudoux.test.spring.aspect.TraceInvocation"/>
<bean id="personneService" class="com.jmdoudoux.test.spring.service.PersonneServiceImpl" />
</beans>
Il faut déclarer dans la configuration le bean qui contient le code de l'aspect à exécuter.
La configuration de l'AOP se fait avec un tag <aop:config>.
Chaque aspect est défini grâce à un tag <aop:aspect> : l'attribut ref permet de préciser l'identifiant du bean qui contient les traitements de l'aspect.
Le point de coupe est défini en utilisant un tag <aop:pointcut>. Son attribut expression permet de définir les méthodes concernées en utilisant une expression régulière.
La définition des points de jonction se fait en utilisant des expressions régulières pour définir les méthodes concernées. Plusieurs caractères particuliers peuvent être utilisés pour définir un filtre sur les classes et la signature des méthodes :
Exemple :
public * *(..) : toutes les méthodes public
* get*(..) : toutes les méthodes commençant par get
* com.jmdoudoux.test.spring.service.IMonService.*(..)) : toutes les méthodes de l'interface IMonService
* com. jmdoudoux.test.spring.service.*.*(..)) : toutes les méthodes du package com. jmdoudoux.test.spring.service
* com. jmdoudoux.test.spring.service..*.*(..)) : toutes les méthodes du package com. jmdoudoux.test.spring.service et de ses sous-packages
Pour pouvoir être utilisé, le namespace aop doit être déclaré dans le tag racine du fichier de définition du contexte.
Exemple : |
<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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
Le schéma AOP propose plusieurs tags pour permettre la définition des aspects dans le fichier de configuration du contexte :
Le tag <aop:config> permet dans le fichier de définition du contexte de configurer Spring AOP. Il peut notamment contenir la définition des points de coupe, des advisors et des aspects. Il est possible d'utiliser plusieurs tags <aop:config> dans un même fichier de configuration. L'ordre de déclaration des points de coupe, des advisors et des aspects doit être respecté à l'intérieur d'un tag <aop:config>.
Il est possible de définir un ou plusieurs points de coupe, chacun étant identifié par un nom unique en utilisant le tag <aop:pointcut>. Le nom est fourni en utilisant l'attribut id. L'attribut expression permet de définir une expression régulière qui va définir le point de coupe. La syntaxe de cette expression est identique à celle utilisée avec les annotations AspectJ. Le tag <aop:pointcut> peut être utilisé comme tags fils du tag <aop:config> ou <aop:aspect>.
La définition d'un advice se fait en utilisant un tag dédié pour chaque advice supporté par Spring AOP (before, after returning, after throwing, after, et around). Ces tags sont à utiliser en tant que tag fils du tag <aop:aspect>.
Le tag <aop:before> permet de définir un advice de type before : cet advice permet d'exécuter une méthode de la classe qui encapsule les traitements de l'aspect juste avant l'exécution des méthodes qui correspondent au point de coupe.
Exemple : |
<aop:config>
<aop:aspect id="traceInvocationAspect" ref="tracerInvocation">
<aop:pointcut id="traceInvocationPointcut"
expression="execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
<aop:before pointcut-ref="traceInvocationPointcut"
method="afficherDebutTrace" />
</aop:aspect>
</aop:config>
<bean id="tracerInvocation" class="com.jmdoudoux.test.spring.aspect.TraceInvocation" />
L'attribut pointcut permet de fournir une expression régulière qui précise le point de coupe.
L'attribut pointcut-ref permet de fournir l'identifiant du point de coupe préalablement défini.
L'attribut method permet de préciser le nom de la méthode de la classe encapsulant les traitements de l'aspect qui sera exécutée. Un paramètre de type org.aspectj.lang.JoinPoint dans la signature de la méthode permet d'obtenir des informations sur le point de jonction.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
public class TraceInvocation {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
public void afficherDebutTrace(final JoinPoint joinpoint) throws Throwable {
final Object[] args = joinpoint.getArgs();
final StringBuffer sb = new StringBuffer();
sb.append(joinpoint.getSignature().toString());
sb.append(" avec les parametres : (");
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
sb.append(")");
LOGGER.info("Debut methode : " + sb);
}
}
Le tag <aop:after-returning> définit un advice de type after returning : cet advice permet d'exécuter une méthode de la classe encapsulant les traitements de l'aspect après l'exécution sans qu'une exception soit levée des méthodes qui correspondent au point de coupe.
Exemple : |
<aop:config>
<aop:aspect id="traceInvocationAspect" ref="tracerInvocation">
<aop:pointcut id="traceInvocationPointcut"
expression="execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
<aop:after-returning pointcut-ref="traceInvocationPointcut"
method="afficherFinNormaleTrace" returning="result" />
</aop:aspect>
</aop:config>
<bean id="tracerInvocation" class="com.jmdoudoux.test.spring.aspect.TraceInvocation" />
L'attribut pointcut-ref permet de fournir l'identifiant du point de coupe préalablement défini.
L'attribut method permet de préciser le nom de la méthode de la classe encapsulant les traitements de l'aspect qui sera exécutée.
L'attribut returning permet de préciser le nom du paramètre de la méthode qui va contenir la valeur de retour de l'exécution de la méthode. Dans ce cas, la méthode de l'aspect doit avoir un paramètre dont le type est identique à celui des valeurs de retour des méthodes du point de coupe. Le nom de ce paramètre doit correspondre à celui fourni dans l'attribut returning. La méthode de l'aspect peut aussi avoir un paramètre optionnel de type org.aspectj.lang.JoinPoint.StaticPart qui permet d'obtenir des informations sur le point de jonction.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint.StaticPart;
public class TraceInvocation {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
public void afficherFinNormaleTrace(final StaticPart staticPart, final Object result)
throws Throwable {
String nomMethode = staticPart.getSignature().toLongString();
LOGGER.info("Fin methode : " + nomMethode + " retour=" + result);
}
}
Le tag <aop:after-throwing> permet de définir un advice de type after throwing : cet advice permet d'invoquer une méthode de la classe qui encapsule les traitements de l'aspect après l'exécution ayant levé une exception des méthodes qui correspondent au point de coupe.
Exemple : |
<aop:config>
<aop:aspect id="traceInvocationAspect" ref="tracerInvocation">
<aop:pointcut id="traceInvocationPointcut"
expression="execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
<aop:after-throwing pointcut-ref="traceInvocationPointcut"
method="afficherExceptionTrace" throwing="exception" />
</aop:aspect>
</aop:config>
<bean id="tracerInvocation" class="com.jmdoudoux.test.spring.aspect.TraceInvocation" />
L'attribut pointcut-ref permet de fournir l'identifiant du point de coupe préalablement défini.
L'attribut method permet de préciser le nom de la méthode de la classe encapsulant les traitements de l'aspect qui sera exécutée.
L'attribut throwing permet de préciser le nom du paramètre de la méthode qui va contenir l'exception levée durant l'exécution. Dans ce cas, la méthode de l'aspect doit avoir un paramètre du type Exception à traiter pour les méthodes du point de coupe dont le nom correspond à celui fourni dans l'attribut throwing. La méthode de l'aspect peut aussi avoir un paramètre optionnel de type org.aspectj.lang.JoinPoint.StaticPart qui permet d'obtenir des informations sur le point de jonction.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint.StaticPart;
public class TraceInvocation {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
public void afficherExceptionTrace(final StaticPart staticPart,
final Exception exception) throws Throwable {
String nomMethode = staticPart.getSignature().toLongString();
LOGGER.error("Exception durant la methode : " + nomMethode, exception);
}
}
Le tag <aop:after> permet de définir un advice de type after : cet advice permet d'invoquer une méthode de la classe qui encapsule les traitements de l'aspect après l'exécution des méthodes qui correspondent au point de coupe qu'une exception soit levée ou non durant leur exécution.
Exemple : |
<aop:config>
<aop:aspect id="traceInvocationAspect" ref="tracerInvocation">
<aop:pointcut id="traceInvocationPointcut"
expression="execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
<aop:after pointcut-ref="traceInvocationPointcut"
method="afficherFinTrace" />
</aop:aspect>
</aop:config>
<bean id="tracerInvocation" class="com.jmdoudoux.test.spring.aspect.TraceInvocation" />
L'attribut pointcut-ref permet de fournir l'identifiant du point de coupe préalablement défini.
L'attribut method permet de préciser le nom de la méthode de la classe encapsulant les traitements de l'aspect qui sera exécutée. Un paramètre de type org.aspectj.lang.JoinPoint dans la signature de la méthode permet d'obtenir des informations sur le point de jonction.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint.StaticPart;
public class TraceInvocation {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
public void afficherFinTrace(final JoinPoint joinpoint) throws Throwable {
final Object[] args = joinpoint.getArgs();
final StringBuffer sb = new StringBuffer();
sb.append(joinpoint.getSignature().toString());
sb.append(" avec les parametres : (");
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
sb.append(")");
LOGGER.info("Fin methode : " + sb);
}
}
Le tag <aop:around> permet de définir un advice de type around : cet advice permet d'invoquer une méthode de la classe qui encapsule les traitements de l'aspect. Cette méthode va permettre de contrôler l'invocation des méthodes qui correspondent au point de coupe qu'une exception soit levée ou non durant leur exécution. Elle permet donc d'exécuter des traitements avant l'invocation, peut conditionner cette invocation et exécuter des traitements suite à cette invocation.
Exemple : |
<aop:config>
<aop:aspect id="traceInvocationAspect" ref="tracerInvocation">
<aop:pointcut id="traceInvocationPointcut"
expression="execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
<aop:around pointcut-ref="traceInvocationPointcut"
method="afficherTrace" />
</aop:aspect>
</aop:config>
<bean id="tracerInvocation" class="com.jmdoudoux.test.spring.aspect.TraceInvocation" />
L'attribut pointcut-ref permet de fournir l'identifiant du point de coupe préalablement défini.
L'attribut method permet de préciser le nom de la méthode de la classe encapsulant les traitements de l'aspect qui sera exécutée. Un paramètre de type org.aspectj.lang.ProceedingJoinPoint dans la signature de la méthode permet d'obtenir des informations sur le point de jonction et de demander l'invocation de la méthode liée au point de jonction en utilisant la méthode proceed().
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
public class TraceInvocation {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
public Object afficherTrace(final ProceedingJoinPoint joinpoint)
throws Throwable {
String nomMethode = joinpoint.getTarget().getClass().getSimpleName() + "."
+ joinpoint.getSignature().getName();
final Object[] args = joinpoint.getArgs();
final StringBuffer sb = new StringBuffer();
sb.append(joinpoint.getSignature().toString());
sb.append(" avec les parametres : (");
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
sb.append(")");
LOGGER.info("Debut methode : " + sb);
try {
Object obj = joinpoint.proceed();
} finally {
LOGGER.info("Fin methode : " + nomMethode + " retour=" + obj);
}
return obj;
}
}
Il est aussi possible d'implémenter l'aspect en utilisant deux points de coupe de type before et after-returning.
Le code de l'aspect doit alors avoir deux méthodes, une pour chaque point de coupe avec leurs signatures respectives.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.core.Ordered;
public class TraceInvocation implements Ordered {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
private int order;
public void afficherDebutTrace(final JoinPoint joinpoint) throws Throwable {
final Object[] args = joinpoint.getArgs();
final StringBuffer sb = new StringBuffer();
sb.append(joinpoint.getSignature().toString());
sb.append(" avec les parametres : (");
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
sb.append(")");
LOGGER.info("Debut methode : " + sb);
}
public void afficherFinTrace(final StaticPart staticPart, final Object result)
throws Throwable {
String nomMethode = staticPart.getSignature().toLongString();
LOGGER.info("Fin methode : " + nomMethode + " retour=" + result);
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(final int order) {
this.order = order;
}
}
La déclaration de l'aspect dans le fichier de configuration utilise les deux points de coupe.
Exemple : |
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.jmdoudoux.test.spring" />
<aop:config>
<aop:aspect id="monitorerPerfAspect" ref="monitorerPerf">
<aop:pointcut id="methodeService"
expression="execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
<aop:around method="executer" pointcut-ref="methodeService" />
</aop:aspect>
<aop:aspect id="traceInvocationAspect" ref="tracerInvocation">
<aop:pointcut id="traceInvocationPointcut"
expression="execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
<aop:before pointcut-ref="traceInvocationPointcut"
method="afficherDebutTrace" />
<aop:after-returning pointcut-ref="traceInvocationPointcut"
method="afficherFinTrace" returning="result" />
</aop:aspect>
</aop:config>
<bean id="monitorerPerf" class="com.jmdoudoux.test.spring.aspect.MonitorePerf">
<property name="order" value="1" />
</bean>
<bean id="tracerInvocation" class="com.jmdoudoux.test.spring.aspect.TraceInvocation">
<property name="order" value="2" />
</bean>
<bean id="personneService" class="com.jmdoudoux.test.spring.service.
PersonneServiceImpl" />
</beans>
L'application de test demande une instance du service à Spring et invoque ses méthodes ajouter() et afficher().
Exemple : |
package com.jmdoudoux.test.spring;
import org.apache.log4j.Logger;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jmdoudoux.test.spring.entite.Personne;
import com.jmdoudoux.test.spring.service.PersonneService;
public class MonApp {
private static Logger LOGGER = Logger.getLogger(MonApp.class);
public static void main(final String[] args) throws Exception {
LOGGER.info("Debut de l'application");
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
new String[] { "appContext.xml" });
PersonneService personneService = (PersonneService) appContext
.getBean("personneService");
LOGGER.info("Debut invocation du service");
try {
personneService.ajouter(new Personne());
} catch (Exception e) {
LOGGER.error("exception " + e.getClass().getName() + " interceptee");
}
personneService.afficher();
LOGGER.info("Fin invocation du service");
LOGGER.info("Fin de l'application");
}
}
Les bibliothèques requises sont : spring-aop 3.0.5, spring-asm 3.0.5, spring-aspect 3.0.5, spring-beans 3.0.5, spring-core 3.0.5, spring-context 3.0.5, spring-expression 3.0.5, aspectjrt 1.6.8, aspectjweaver 1.6.8, aopalliance 1.0, commons-logging 1.1.1, log4j 1.2.16
La bibliothèque aspectjrt est requise car certaines classes sont utilisées lors de la mise en oeuvre de Spring AOP notamment dans le code de l'aspect.
Résultat : |
2011-07-03 19:07:31,671 INFO [com.jmdoudoux.test.spring.MonApp] Debut de l'application
2011-07-03 19:07:32,718 INFO [com.jmdoudoux.test.spring.MonApp] Debut invocation du service
2011-07-03 19:07:32,718 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation] Debut methode
: void com.jmdoudoux.test.spring.service.PersonneService.ajouter(Personne) avec les
parametres : (com.jmdoudoux.test.spring.entite.Personne@26d607)
2011-07-03 19:07:33,218 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation] Fin methode :
public abstract void com.jmdoudoux.test.spring.service.PersonneService.ajouter(
com.jmdoudoux.test.spring.entite.Personne)
retour=null
2011-07-03 19:07:33,218 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation] Debut methode
: void com.jmdoudoux.test.spring.service.PersonneService.afficher() avec les parametres : ()
2011-07-03 19:07:33,468 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation] Fin methode :
public abstract void com.jmdoudoux.test.spring.service.PersonneService.afficher() retour=null
2011-07-03 19:07:33,468 INFO [com.jmdoudoux.test.spring.MonApp] Fin invocation du service
2011-07-03 19:07:33,468 INFO [com.jmdoudoux.test.spring.MonApp] Fin de l'application
Il est possible de définir plusieurs aspects sur un même point de coupe. Dans ce cas, il peut être nécessaire de définir l'ordre d'exécution des aspects.
L'exemple de cette section va définir un aspect pour mesurer le temps d'exécution d'une méthode qui sera invoquée au même endroit que l'aspect qui trace les invocations.
Les aspects doivent alors implémenter l'interface Ordered qui ne définit qu'une seule méthode getOrder() renvoyant un entier.
Le plus simple est de définir un setter sur un champ order, ce qui va permettre de configurer la valeur du numéro d'ordre dans la configuration du contexte.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.core.Ordered;
import org.springframework.util.StopWatch;
public class MonitorePerf implements Ordered {
private static Logger LOGGER = Logger.getLogger(MonitorePerf.class);
private int order;
public Object executer(final ProceedingJoinPoint joinpoint) throws Throwable {
Object returnValue;
StopWatch clock = new StopWatch(getClass().getName());
try {
clock.start(joinpoint.toString());
returnValue = joinpoint.proceed();
} finally {
clock.stop();
LOGGER.info("temps d'execution : " + clock.prettyPrint());
}
return returnValue;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(final int order) {
this.order = order;
}
}
Dans le fichier de configuration du contexte, le second aspect est défini et l'ordre est précisé pour les deux aspects en utilisant leur propriété order.
Exemple : |
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.jmdoudoux.test.spring" />
<aop:config>
<aop:aspect id="monitorerPerfAspect" ref="monitorerPerf">
<aop:pointcut id="methodeService"
expression="execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
<aop:around method="executer" pointcut-ref="methodeService" />
</aop:aspect>
<aop:aspect id="traceInvocationAspect" ref="tracerInvocation">
<aop:pointcut id="traceInvocationPointcut"
expression="execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
<aop:around pointcut-ref="traceInvocationPointcut" method="afficherTrace" />
</aop:aspect>
</aop:config>
<bean id="monitorerPerf" class="com.jmdoudoux.test.spring.aspect.MonitorePerf">
<property name="order" value="1" />
</bean>
<bean id="tracerInvocation" class="com.jmdoudoux.test.spring.aspect.TraceInvocation">
<property name="order" value="2" />
</bean>
<bean id="personneService" class="com.jmdoudoux.test.spring.service.PersonneServiceImpl" />
</beans>
Lors de l'exécution de l'exemple, les aspects sont invoqués dans l'ordre précisé.
Résultat : |
2011-06-26 17:48:40,890 INFO [com.jmdoudoux.test.spring.MonApp] Debut de
l'application
2011-06-26 17:48:41,921 INFO [com.jmdoudoux.test.spring.MonApp] Debut
invocation du service
2011-06-26 17:48:41,921 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Debut methode : void com.jmdoudoux.test.spring.service.PersonneService.ajouter(
Personne) avec les parametres : (com.jmdoudoux.test.spring.entite.Personne@419d05)
2011-06-26 17:48:42,421 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Fin methode : PersonneServiceImpl.ajouter retour=null
2011-06-26 17:48:42,421 INFO [com.jmdoudoux.test.spring.aspect.MonitorePerf]
temps d'execution : StopWatch 'com.jmdoudoux.test.spring.aspect.MonitorePerf':
running time (millis) = 500
-----------------------------------------
ms % Task name
-----------------------------------------
00500 100 % execution(void com.jmdoudoux.test.spring.service.PersonneService.
ajouter(Personne))
2011-06-26 17:48:42,421 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Debut methode : void com.jmdoudoux.test.spring.service.PersonneService.afficher(
) avec les parametres : ()
2011-06-26 17:48:42,671 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Fin methode : PersonneServiceImpl.afficher retour=null
2011-06-26 17:48:42,671 INFO [com.jmdoudoux.test.spring.aspect.MonitorePerf]
temps d'execution : StopWatch 'com.jmdoudoux.test.spring.aspect.MonitorePerf':
running time (millis) = 250
-----------------------------------------
ms % Task name
-----------------------------------------
00250 100 % execution(void com.jmdoudoux.test.spring.service.PersonneService.
afficher())
2011-06-26 17:48:42,671 INFO [com.jmdoudoux.test.spring.MonApp] Fin invocation
du service
2011-06-26 17:48:42,671 INFO [com.jmdoudoux.test.spring.MonApp] Fin de
l'application
Il est possible de simplifier encore plus le fichier de configuration en utilisant les annotations pour définir les beans et les aspects puisque les aspects sont aussi des beans.
La mise en oeuvre de Spring AOP peut se faire en utilisant les annotations d'AspectJ pour réaliser sa définition. Bien que ce soit les annotations d'AspectJ qui sont utilisées, le tissage ne va pas être réalisé avec AspectJ mais avec Spring AOP.
L'utilisation des annotations d'AspectJ requiert un Java SE 5 ou ultérieur.
La classe qui contient les traitements de l'aspect utilise les annotations d'AspectJ pour définir l'aspect, le point de coupe et les points de jonction. La configuration est dans ce cas simplifiée.
La classe de l'aspect doit être annotée avec @Aspect.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class TraceInvocation {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
@Around("traceInvocationPointcut()")
public Object afficherTrace(final ProceedingJoinPoint joinpoint)
throws Throwable {
String nomMethode = joinpoint.getTarget().getClass().getSimpleName() + "."
+ joinpoint.getSignature().getName();
final Object[] args = joinpoint.getArgs();
final StringBuffer sb = new StringBuffer();
sb.append(joinpoint.getSignature().toString());
sb.append(" avec les parametres : (");
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
sb.append(")");
LOGGER.info("Debut methode : " + sb);
try {
Object obj = joinpoint.proceed();
} finally {
LOGGER.info("Fin methode : " + nomMethode + " retour=" + obj);
}
return obj;
}
@Pointcut("execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))")
public void traceInvocationPointcut() {
}
}
Le code de l'aspect à exécuter doit être contenu dans une méthode dont la signature est particulière et dépend de l'annotation utilisée pour préciser son point de jonction. Dans l'exemple ci-dessus, elle attend en paramètre un objet de type ProceedingJoinPoint puisque l'annotation utilisée est @Around.
L'annotation @Pointcut permet de définir des points de coupe. Elle s'utilise sur une méthode sans traitement d'une classe ou d'une interface annotée avec @Aspect. Cette classe ou interface peut avoir plusieurs méthodes annotées avec @Pointcut.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
@Aspect
public interface ITraceInvocation {
@Pointcut("execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))")
void traceInvocationPointcut();
}
Comme la définition de l'aspect est faite avec des annotations, le fichier de configuration est grandement simplifié.
Pour utiliser des aspects définis avec les annotations d'AspectJ par Spring AOP, il faut utiliser le tag <aop:aspectj-autoproxy> dans le fichier de configuration. La classe qui encapsule l'aspect doit aussi être définie dans la configuration du contexte.
Exemple : |
<?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:tx="http://www.springframework.org/schema/tx"
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">
<aop:aspectj-autoproxy/>
<bean id="tracerInvocation" class="com.jmdoudoux.test.spring.aspect.TraceInvocation">
</bean>
<bean id="personneService" class="com.jmdoudoux.test.spring.service.PersonneServiceImpl" />
</beans>
Remarque : bien que les annotations d'AspectJ soient utilisées, le tissage n'est pas réalisé par AspectJ mais par Spring AOP en créant dynamiquement des proxys.
Les bibliothèques requises sont : spring-aop 3.0.5, spring-asm 3.0.5, spring-aspect 3.0.5, spring-beans 3.0.5, spring-core 3.0.5, spring-context 3.0.5, spring-expression 3.0.5, aspectjrt 1.6.8, aspectjweaver 1.6.8, aopalliance 1.0, commons-logging 1.1.1, log4j 1.2.16
Il est possible de simplifier encore plus le fichier de définition du contexte en déclarant les beans du service et des aspects grâce aux annotations. Pour cela, il faut permettre au conteneur Spring de détecter automatiquement ces beans, même les aspects, en les annotant avec @Component. Il est très important que le bean de l'aspect soit déclaré dans le contexte pour permettre à Spring AOP de créer le proxy requis : si l'aspect n'est pas annoté avec @Component, l'aspect ne sera tout simplement pas exécuté.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
@Component
public class TraceInvocation {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
public void afficherDebutTrace(final JoinPoint joinpoint) throws Throwable {
final Object[] args = joinpoint.getArgs();
final StringBuffer sb = new StringBuffer();
sb.append(joinpoint.getSignature().toString());
sb.append(" avec les parametres : (");
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
sb.append(")");
LOGGER.info("Debut methode : " + sb);
}
public void afficherFinTrace(final StaticPart staticPart, final Object result)
throws Throwable {
String nomMethode = staticPart.getSignature().toLongString();
LOGGER.info("Fin methode : " + nomMethode + " retour=" + result);
}
@Around("traceInvocationPointcut()")
public Object afficherTrace(final ProceedingJoinPoint joinpoint)
throws Throwable {
String nomMethode = joinpoint.getTarget().getClass().getSimpleName() + "."
+ joinpoint.getSignature().getName();
final Object[] args = joinpoint.getArgs();
final StringBuffer sb = new StringBuffer();
sb.append(joinpoint.getSignature().toString());
sb.append(" avec les parametres : (");
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
sb.append(")");
LOGGER.info("Debut methode : " + sb);
try {
Object obj = joinpoint.proceed();
} finally {
LOGGER.info("Fin methode : " + nomMethode + " retour=" + obj);
}
return obj;
}
@Pointcut("execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))")
public void traceInvocationPointcut() {
}
}
Le fichier de configuration est alors minimaliste.
Exemple : |
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.jmdoudoux.test.spring" />
<aop:aspectj-autoproxy/>
</beans>
Le résultat de l'exécution est le même.
Il est possible d'implémenter l'aspect en utilisant deux points de coupe de types before et after-returning et leurs annotations respectives.
Le code de l'aspect doit alors avoir deux méthodes, une pour chaque point de coupe avec leurs signatures respectives.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class TraceInvocation {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
@Before("traceInvocationPointcut()")
public void afficherDebutTrace(final JoinPoint joinpoint) throws Throwable {
final Object[] args = joinpoint.getArgs();
final StringBuffer sb = new StringBuffer();
sb.append(joinpoint.getSignature().toString());
sb.append(" avec les parametres : (");
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
sb.append(")");
LOGGER.info("Debut methode : " + sb);
}
@AfterReturning(pointcut = "traceInvocationPointcut()", returning = "result")
public void afficherFinTrace(final StaticPart staticPart, final Object result)
throws Throwable {
String nomMethode = staticPart.getSignature().toLongString();
LOGGER.info("Fin methode : " + nomMethode + " retour=" + result);
}
@Pointcut("execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))")
public void traceInvocationPointcut() {
}
}
Attention : toutes les fonctionnalités d'AspectJ ne sont pas prises en charge par Spring AOP. Une exception est levée si Spring AOP rencontre une fonctionnalité non supportée.
Résultat : |
Caused by:
java.lang.IllegalArgumentException: DeclarePrecendence not presently supported
in Spring AOP
La gestion de l'ordre des aspects définis avec les annotations AspectJ se fait de la même façon que pour les aspects définis dans la configuration du contexte puisqu'au final dans les deux cas, c'est Spring AOP qui prend en charge les aspects. Il faut aussi utiliser l'interface Ordered qui possède une seule méthode getOrder(). Cette méthode getOrder() doit renvoyer le numéro d'ordre d'exécution de l'aspect.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TraceInvocation implements Ordered {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
private int order;
@Around("traceInvocationPointcut()")
public Object afficherTrace(final ProceedingJoinPoint joinpoint)
throws Throwable {
String nomMethode = joinpoint.getTarget().getClass().getSimpleName() + "."
+ joinpoint.getSignature().getName();
final Object[] args = joinpoint.getArgs();
final StringBuffer sb = new StringBuffer();
sb.append(joinpoint.getSignature().toString());
sb.append(" avec les parametres : (");
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
sb.append(")");
LOGGER.info("Debut methode : " + sb);
try {
Object obj = joinpoint.proceed();
}
finally {
LOGGER.info("Fin methode : " + nomMethode + " retour=" + obj);
}
return obj;
}
@Override
public int getOrder() {
return order;
}
@Value("2")
public void setOrder(final int order) {
this.order = order;
}
@Pointcut("execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))")
public void traceInvocationPointcut() {
}
}
Le second aspect est défini avec son propre numéro d'ordre d'invocation.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Component
@Aspect
public class MonitorePerf implements Ordered {
private static Logger LOGGER = Logger.getLogger(MonitorePerf.class);
private int order;
@Around("monitorePerfPointcut()")
public Object executer(final ProceedingJoinPoint joinpoint) throws Throwable {
Object returnValue;
StopWatch clock = new StopWatch(getClass().getName());
try {
clock.start(joinpoint.toString());
returnValue = joinpoint.proceed();
} finally {
clock.stop();
LOGGER.info("temps d'execution : " + clock.prettyPrint());
}
return returnValue;
}
@Override
public int getOrder() {
return order;
}
@Pointcut("execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))")
public void monitorePerfPointcut() {
}
@Value("1")
public void setOrder(final int order) {
this.order = order;
}
}
Il faut être attentif au numéro d'ordre attribué à chaque aspect selon le type d'advice utilisé et le résultat souhaité. Dans l'exemple ci-dessus, le but est d'avoir dans les logs les traces d'exécution suivies des informations sur le temps d'exécution. C'est pourtant l'aspect de monitoring qui possède le numéro d'ordre d'exécution 1 puisque l'aspect utilise l'advice around.
Comme toute la configuration est faite avec des annotations, le fichier de définition du contexte est toujours aussi simple.
Exemple : |
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.jmdoudoux.test.spring" />
<aop:aspectj-autoproxy/>
</beans>
Lors de l'exécution, l'ordre est respecté.
Résultat : |
2011-07-10 16:49:25,546 INFO [com.jmdoudoux.test.spring.MonApp] Debut de l'application
2011-07-10 16:49:26,546 INFO [com.jmdoudoux.test.spring.MonApp] Debut invocation du service
2011-07-10 16:49:26,578 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Debut methode : void com.jmdoudoux.test.spring.service.PersonneService.ajouter(Personne)
avec les parametres : (com.jmdoudoux.test.spring.entite.Personne@55a338)
2011-07-10 16:49:27,078 INFO
[com.jmdoudoux.test.spring.aspect.TraceInvocation]
Fin methode : PersonneServiceImpl.ajouter retour=null
2011-07-10 16:49:27,078 INFO [com.jmdoudoux.test.spring.aspect.MonitorePerf]
temps d'execution :
StopWatch 'com.jmdoudoux.test.spring.aspect.MonitorePerf':
running time (millis) = 500
-----------------------------------------
ms %
Task name
-----------------------------------------
00500 100 %
execution(void com.jmdoudoux.test.spring.service.PersonneService.
ajouter(Personne))
2011-07-10 16:49:27,078 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Debut methode : void com.jmdoudoux.test.spring.service.PersonneService.afficher()
avec les parametres : ()
2011-07-10 16:49:27,328 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Fin methode : PersonneServiceImpl.afficher retour=null
2011-07-10 16:49:27,328 INFO
[com.jmdoudoux.test.spring.aspect.MonitorePerf]
temps d'execution :
StopWatch 'com.jmdoudoux.test.spring.aspect.MonitorePerf':
running time (millis) = 250
-----------------------------------------
ms %
Task name
-----------------------------------------
00250 100 %
execution(void com.jmdoudoux.test.spring.service.PersonneService.afficher())
2011-07-10 16:49:27,328 INFO [com.jmdoudoux.test.spring.MonApp] Fin invocation du service
2011-07-10 16:49:27,328 INFO [com.jmdoudoux.test.spring.MonApp] Fin de l'application
Pour modifier cet ordre, il suffit de changer la valeur de la propriété order. L'ordre d'exécution des aspects est alors inversé.
Résultat : |
2011-07-10 16:57:57,703 INFO [com.jmdoudoux.test.spring.MonApp] Debut de l'application
2011-07-10 16:57:58,718 INFO [com.jmdoudoux.test.spring.MonApp] Debut invocation du service
2011-07-10 16:57:58,750 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Debut methode : void com.jmdoudoux.test.spring.service.PersonneService.ajouter( Personne)
avec les parametres : (com.jmdoudoux.test.spring.entite.Personne@ b1074a)
2011-07-10 16:57:59,250 INFO [com.jmdoudoux.test.spring.aspect.MonitorePerf]
temps d'execution :
StopWatch 'com.jmdoudoux.test.spring.aspect.MonitorePerf':
running time (millis) = 500
-----------------------------------------
ms %
Task name
-----------------------------------------
00500 100 %
execution(void com.jmdoudoux.test.spring.service.PersonneService.
ajouter(Personne))
2011-07-10 16:57:59,250 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Fin methode : PersonneServiceImpl.ajouter retour=null
2011-07-10 16:57:59,250 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Debut methode : void com.jmdoudoux.test.spring.service.PersonneService.afficher()
avec les parametres : ()
2011-07-10 16:57:59,500 INFO [com.jmdoudoux.test.spring.aspect.MonitorePerf]
temps d'execution :
StopWatch 'com.jmdoudoux.test.spring.aspect.MonitorePerf':
running time (millis) = 250
-----------------------------------------
ms %
Task name
-----------------------------------------
00250 100 %
execution(void com.jmdoudoux.test.spring.service.PersonneService.afficher())
2011-07-10 16:57:59,500 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Fin methode : PersonneServiceImpl.afficher retour=null
2011-07-10 16:57:59,500 INFO [com.jmdoudoux.test.spring.MonApp] Fin invocation du service
2011-07-10 16:57:59,500 INFO [com.jmdoudoux.test.spring.MonApp] Fin de l'application
Spring permet aussi une utilisation d'AspectJ pour mettre en oeuvre l'AOP : AspectJ propose un support très complet des possibilités offertes par l'AOP.
AspectJ utilise sa propre syntaxe pour la création d'un aspect mais surtout les aspects peuvent être tissés au runtime avec un agent dédié (classloader qui enrichit le bytecode lors de son chargement) ou à la compilation avec le compilateur dédié d'AspectJ.
Exemple : |
public aspect HelloAspectJ {
pointcut methodeMain() : execution(* main(..));
after() returning : methodMain() {
System.out.println("Hello AspectJ!");
}
}
AspectJ 5 permet la création d'un aspect sous la forme d'une simple classe annotée avec des annotations dédiées comme @Aspect. L'aspect ci-dessus peut ainsi être défini avec l'annotation @Aspect.
Exemple : |
@Aspect
public class HelloAspectJ {
@Pointcut("execution(* main(..))")
public void methodeMain() {}
@AfterReturning("methodeMain()")
public void saluer() {
System.out.println("Hello AspectJ!");
}
}
Avec cette solution, le tissage des aspects va être réalisé dynamiquement, aux chargements des classes concernées, par un agent d'AspectJ.
Le code de l'application, du service, du bean et de l'aspect sont les mêmes que dans l'exemple utilisant Spring AOP avec les annotations d'AspectJ. Les différences vont se faire au niveau de la configuration du contexte Spring, des bibliothèques requises et de l'utilisation de l'agent dans la JVM.
Le fichier de configuration du contexte n'a plus besoin de contenir des définitions relatives aux aspects.
Exemple : |
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config />
<context:spring-configured />
<context:component-scan base-package="com.jmdoudoux.test.spring" />
</beans>
Il faut définir un fichier META-INF/aop.xml accessible par le classpath qui va contenir les informations sur le tissage à réaliser par AspectJ.
Exemple : |
<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
<aspects>
<aspect name="com.jmdoudoux.test.spring.aspect.MonitorePerf" />
<aspect name="com.jmdoudoux.test.spring.aspect.TraceInvocation" />
</aspects>
<weaver options="-XnoInline -Xlint:ignore -verbose -showWeaveInfo">
<include name="com.jmdoudoux.test.spring.service..*" />
</weaver>
</aspectj>
Le tag racine de ce fichier de configuration est le tag <aspectj>.
Les aspects doivent être déclarés chacun dans un tag <aspect> fils du tag <aspects>. L'attribut name permet de préciser le nom pleinement qualifié de l'aspect.
Le tag weaver permet de configurer le tissage des aspects. L'attribut options permet de définir les options du tisseur.
Les options de tissage «-verbose » et « -showWeaveInfo » sont particulièrement utiles dans l'environnement de développement pour obtenir des informations sur les opérations réalisées par AspectJ (enregistrement des aspects, leur tissage, ...).
Le tag fils <include> permet de préciser sous la forme d'une expression régulière les classes qui sont concernées par le tissage.
Il faut lancer la JVM avec l'option -javaagent:chemin_vers_aspectjweaver.jar
exemple :
-javaagent:lib/aspectjweaver-1.6.1.jar
Le classpath de l'application contient les bibliothèques : org.springframework.asm-3.0.5.RELEASE.jar, org.springframework.aspects-3.0.5.RELEASE.jar, org.springframework.beans-3.0.5.RELEASE.jar, org.springframework.context-3.0.5.RELEASE.jar, org.springframework.core-3.0.5.RELEASE.jar, org.springframework.expression-3.0.5.RELEASE.jar, org.apache.commons.logging-1.1.1.jar, aspectrt.jar, log4j-1.2.16.jar
Résultat : |
2011-08-09 18:57:48,968 INFO [com.jmdoudoux.test.spring.MonApp] Debut de
l'application
2011-08-09 18:57:49,265 INFO [org.springframework.context.support.
ClassPathXmlApplicationContext] Refreshing org.springframework.context.support.
ClassPathXmlApplicationContext@2d189c: startup date [Tue Aug 09 18:57:49 CEST
2011]; root of context hierarchy
2011-08-09 18:57:49,578 INFO [org.springframework.beans.factory.xml.
XmlBeanDefinitionReader] Loading XML bean definitions from class path resource [
appContext.xml]
2011-08-09 18:57:50,515 INFO [org.springframework.beans.factory.support.
DefaultListableBeanFactory] Pre-instantiating singletons in org.springframework.
beans.factory.support.DefaultListableBeanFactory@e8a0cd: defining beans [org.
springframework.context.annotation.internalConfigurationAnnotationProcessor,org.
springframework.context.annotation.internalAutowiredAnnotationProcessor,org.
springframework.context.annotation.internalRequiredAnnotationProcessor,org.
springframework.context.annotation.internalCommonAnnotationProcessor,org.
springframework.context.config.internalBeanConfigurerAspect,personne1,personne2,
personneService]; root of factory hierarchy
Invocation constructeur PersonneServiceImpl()
2011-08-09 18:57:50,656 INFO [com.jmdoudoux.test.spring.MonApp] Debut
invocation du service
2011-08-09 18:57:50,687 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Debut methode : void com.jmdoudoux.test.spring.service.PersonneServiceImpl.
ajouter(Personne) avec les parametres : (com.jmdoudoux.test.spring.entite.
Personne@12f195)
2011-08-09 18:57:51,187 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Fin methode : public void com.jmdoudoux.test.spring.service.
PersonneServiceImpl.ajouter(com.jmdoudoux.test.spring.entite.Personne) retour=
null
2011-08-09 18:57:51,187 INFO [com.jmdoudoux.test.spring.aspect.MonitorePerf]
temps d'execution : StopWatch 'com.jmdoudoux.test.spring.aspect.MonitorePerf':
running time (millis) = 516
-----------------------------------------
ms % Task name
-----------------------------------------
00516 100 % execution(void com.jmdoudoux.test.spring.service.
PersonneServiceImpl.ajouter(Personne))
2011-08-09 18:57:51,187 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Debut methode : void com.jmdoudoux.test.spring.service.PersonneServiceImpl.
afficher() avec les parametres : ()
2011-08-09 18:57:51,437 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Fin methode : public void com.jmdoudoux.test.spring.service.
PersonneServiceImpl.afficher() retour=null
2011-08-09 18:57:51,437 INFO [com.jmdoudoux.test.spring.aspect.MonitorePerf]
temps d'execution : StopWatch 'com.jmdoudoux.test.spring.aspect.MonitorePerf':
running time (millis) = 250
-----------------------------------------
ms % Task name
-----------------------------------------
00250 100 % execution(void com.jmdoudoux.test.spring.service.
PersonneServiceImpl.afficher())
2011-08-09 18:57:51,437 INFO [com.jmdoudoux.test.spring.MonApp] Fin invocation
du service
2011-08-09 18:57:51,437 INFO [com.jmdoudoux.test.spring.MonApp] Fin de
l'application
L'avantage de cette solution est qu'elle permet d'ajouter des aspects sur des beans qui ne sont pas gérés par Spring.
Il est possible dans une même application d'utiliser des aspects tissés par Spring AOP et par AspectJ avec LTW.
Lorsque les aspects sont définis en utilisant les annotations d'AspectJ, il est nécessaire de préciser les aspects qui seront tissés par Spring AOP et ceux qui le seront par AspectJ.
Les aspects pris en charge par Spring AOP doivent être précisés en utilisant le tag <aop:include> dans le fichier de configuration du contexte de Spring.
Les aspects pris en charge par AspectJ doivent être précisés dans le fichier aop.xml pour un tissage dynamique.
Ainsi chaque tisseur prendra en charge les aspects qui le concernent.
L'exemple ci-dessous va mettre en oeuvre un aspect avec Spring AOP et un autre avec AspectJ.
Exemple : |
@Component("traceInvocation")
@Aspect
public class TraceInvocation {
private static Logger LOGGER = Logger.getLogger(TraceInvocation.class);
@Around("traceInvocationPointcut()")
public Object afficherTrace(final ProceedingJoinPoint joinpoint)
throws Throwable {
String nomMethode = joinpoint.getTarget().getClass().getSimpleName() + "."
+ joinpoint.getSignature().getName();
final Object[] args = joinpoint.getArgs();
final StringBuffer sb = new StringBuffer();
sb.append(joinpoint.getSignature().toString());
sb.append(" avec les parametres : (");
for (int i = 0; i < args.length; i++) {
sb.append(args[i]);
if (i < args.length - 1) {
sb.append(", ");
}
}
sb.append(")");
LOGGER.info("Debut methode : " + sb);
Object obj = joinpoint.proceed();
LOGGER.info("Fin methode : " + nomMethode + " retour=" + obj);
return obj;
}
@Pointcut("execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))")
public void traceInvocationPointcut() {
}
}
Cet aspect va être pris en charge par Spring AOP. Dans le fichier de déclaration du contexte, le bean qui encapsule l'aspect est fourni comme valeur de l'attribut name du tag <aop:include>. Ce tag est un tag fils du tag <aop:aspectj-autoproxy>
Exemple : |
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.jmdoudoux.test.spring" />
<aop:aspectj-autoproxy>
<aop:include name="traceInvocation" />
</aop:aspectj-autoproxy>
</beans>
Le second aspect est écrit de manière similaire en incluant en plus une gestion de son ordre d'exécution par rapport aux autres aspects.
Exemple : |
package com.jmdoudoux.test.spring.aspect;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Component
@Aspect
public class MonitorePerf implements Ordered {
private static Logger LOGGER = Logger.getLogger(MonitorePerf.class);
private int order;
@Around("monitorePerfPointcut()")
public Object executer(final ProceedingJoinPoint joinpoint) throws Throwable {
Object returnValue;
StopWatch clock = new StopWatch(getClass().getName());
try {
clock.start(joinpoint.toString());
returnValue = joinpoint.proceed();
} finally {
clock.stop();
LOGGER.info("temps d'execution : " + clock.prettyPrint());
}
return returnValue;
}
@Override
public int getOrder() {
return order;
}
@Pointcut("execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))")
public void monitorePerfPointcut() {
}
@Value("2")
public void setOrder(final int order) {
this.order = order;
}
}
Cet aspect est pris en charge par AspectJ grâce à une configuration définie dans un fichier nommé aop-ajc.xml stocké dans le sous-répertoire META-INF.
Exemple : |
<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
<weaver options="-XnoInline -Xlint:ignore -verbose -showWeaveInfo">
<include name="com.jmdoudoux.test.spring.service.*ServiceImpl"/>
</weaver>
<aspects>
<aspect name="com.jmdoudoux.test.spring.aspect.MonitorePerf"/>
</aspects>
</aspectj>
Ce fichier de configuration d'AspectJ permet de préciser :
Les autres fichiers sont identiques à ceux des exemples précédents.
Pour permettre le tissage au runtime par AspectJ, il est nécessaire de préciser l'agent adéquat à la JVM en utilisant l'option -javaagent:chemin_vers_aspectjweaver.jar
Exemple :
-javaagent:lib/aspectjweaver-1.6.1.jar
Le classpath de l'application contient les bibliothèques : org.springframework.aop-3.0.5.RELEASE.jar, org.springframework.asm-3.0.5.RELEASE.jar, org.springframework.aspects-3.0.5.RELEASE.jar, org.springframework.beans-3.0.5.RELEASE.jar, org.springframework.context-3.0.5.RELEASE.jar, org.springframework.core-3.0.5.RELEASE.jar, org.springframework.expression-3.0.5.RELEASE.jar, org.springframework.instrument-3.0.5.RELEASE.jar, org.apache.commons.logging-1.1.1.jar, aspectrt.jar, log4j-1.2.16.jar, aopalliance-1.0.0.jar, com.springframework.org.aspectj.weaver.-.1.6.8.RELEASE.jar,
Résultat : |
[AppClassLoader@fabe9] info AspectJ Weaver Version 1.6.8
built on Friday Jan 8, 2010 at 21:53:37 GMT
[AppClassLoader@fabe9] info register classloader sun.misc.Launcher$AppClassLoader@fabe9
[AppClassLoader@fabe9] info using configuration
file:/C:/java/api/spring-framework-3.0.5.RELEASE/dist/
org.springframework.aspects-3.0.5.RELEASE.jar!/META-INF/aop.xml
[AppClassLoader@fabe9] info using configuration
/C:/Documents%20and%20Settings/
jm/Documents/workspace-sts-2.5.1.RELEASE/TestSpringAOPetAspectJ/bin/META-INF/aop-ajc.xml
[AppClassLoader@fabe9] info register aspect org.springframework.beans.factory.
aspectj.AnnotationBeanConfigurerAspect
[AppClassLoader@fabe9] info register aspect org.springframework.scheduling.
aspectj.AnnotationAsyncExecutionAspect
[AppClassLoader@fabe9] info register aspect org.springframework.transaction.
aspectj.AnnotationTransactionAspect
[AppClassLoader@fabe9] info register aspect com.jmdoudoux.test.spring.aspect.
MonitorePerf
2011-07-04 19:25:13,140 INFO [com.jmdoudoux.test.spring.MonApp] Debut de l'application
2011-07-04 19:25:13,406 INFO [org.springframework.context.support.
ClassPathXmlApplicationContext] Refreshing org.springframework.context.support.
ClassPathXmlApplicationContext@1860038: startup date [Tue Sep 06 19:25:13 CEST 2011];
root of context hierarchy
2011-07-04 19:25:13,687 INFO [org.springframework.beans.factory.xml.
XmlBeanDefinitionReader] Loading XML bean
definitions from class path resource [appContext.xml]
[AppClassLoader@fabe9] weaveinfo Join point 'method-execution(void com.
jmdoudoux.test.spring.service.PersonneServiceImpl.afficher())'in Type 'com.
jmdoudoux.test.spring.service.PersonneServiceImpl'
(PersonneServiceImpl.java:17)
advised by around advice from 'com.jmdoudoux.test.spring.aspect.MonitorePerf'
(MonitorePerf.java)
[AppClassLoader@fabe9] weaveinfo Join point 'method-execution(void com.
jmdoudoux.test.spring.service.PersonneServiceImpl.ajouter(com.jmdoudoux.test.
spring.entite.Personne))' in Type 'com.jmdoudoux.test.spring.service.
PersonneServiceImpl'(PersonneServiceImpl.java:27) advised by around advice from
'com.jmdoudoux.test.spring.aspect.MonitorePerf' (MonitorePerf.java)
2011-07-04 19:25:14,750 INFO [org.springframework.beans.factory.support.
DefaultListableBeanFactory] Pre-instantiating singletons in org.springframework.
beans.factory.support.DefaultListableBeanFactory@79801c:
defining beans [
monitorePerf,traceInvocation,personne1,personne2,personneService,org.
springframework.context.annotation.internalConfigurationAnnotationProcessor,org.
springframework.context.annotation.internalAutowiredAnnotationProcessor,org.
springframework.context.annotation.internalRequiredAnnotationProcessor,org.
springframework.context.annotation.internalCommonAnnotationProcessor,org.
springframework.aop.config.internalAutoProxyCreator];
root of factory hierarchy
Invocation constructeur PersonneServiceImpl()
2011-07-04 19:25:15,015 INFO [com.jmdoudoux.test.spring.MonApp] Debut invocation du service
2011-07-04 19:25:15,046 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Debut methode : void com.jmdoudoux.test.spring.service.PersonneService.ajouter(
Personne) avec les parametres : (com.jmdoudoux.test.spring.entite.Personne@6e96ff)
2011-07-04 19:25:15,546 INFO [com.jmdoudoux.test.spring.aspect.MonitorePerf]
temps d'execution : StopWatch 'com.jmdoudoux.test.spring.aspect.MonitorePerf':
running time (millis) = 500
-----------------------------------------
ms
% Task name
-----------------------------------------
00500 100
% execution(void com.jmdoudoux.test.spring.service.PersonneServiceImpl.ajouter(Personne))
2011-07-04 19:25:15,546 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Fin methode :
PersonneServiceImpl.ajouter retour=null
2011-07-04 19:25:15,546 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Debut methode : void com.jmdoudoux.test.spring.service.PersonneService.afficher(
) avec les parametres : ()
2011-07-04 19:25:15,796 INFO [com.jmdoudoux.test.spring.aspect.MonitorePerf]
temps d'execution : StopWatch 'com.jmdoudoux.test.spring.aspect.MonitorePerf':
running time (millis) = 250
-----------------------------------------
ms
% Task name
-----------------------------------------
00250 100
% execution(void com.jmdoudoux.test.spring.service.PersonneServiceImpl.afficher())
2011-07-04 19:25:15,796 INFO [com.jmdoudoux.test.spring.aspect.TraceInvocation]
Fin methode :
PersonneServiceImpl.afficher retour=null
2011-07-04 19:25:15,796 INFO [com.jmdoudoux.test.spring.MonApp] Fin invocation du service
2011-07-04 19:25:15,796 INFO [com.jmdoudoux.test.spring.MonApp] Fin de l'application
Depuis la version 2.5 de Spring, il est possible d'utiliser le tag <context:load-time-weaver> dans le fichier de configuration pour demander le tissage en utilisant un agent fourni par Spring plutôt que l'agent d'AspectJ.
Attention : le tissage par l'agent Spring ne peut se faire que sur des objets qui sont définis dans le contexte et donc gérés dans le conteneur.
La bibliothèque spring-agent doit être ajoutée au classpath.
La JVM doit être lancée avec l'option -javaagent:chemin bibliothèque spring-agent.jar
Si le tag <context:load-time-weaver> est utilisé dans une application web déployée dans un conteneur web Tomcat, il faut préciser l'utilisation d'un classloader dédié dans le fichier META-INF/context.xml de la webapp
Exemple : |
<?xml version='1.0' encoding='utf-8'?>
<Context reloadable="true">
<Loader loaderClass=
"org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
</Context>