Ce tutoriel a pour but de présenter une des nouvelles fonctionnalités apportées par la spécification EJB 3.0 :
les interceptors.
Un interceptor est une méthode qui intercepte l'appel d'une méthode métier.
Les interceptors fonctionnent à la manière des filtres avec les servlets.
Ils sont automatiquement appelés, juste avant que les méthodes métiers du bean soient appelées.
Ils peuvent être appliqués à des méthodes métiers dans des session beans (stateful et stateless) et dans des message-driven beans.
On peut définir une méthode interceptor dans une classe bean ou dans une classe dédiée.
La signature d'une méthode interceptor est la suivante:
@javax.ejb.AroundInvoke
public Object [nomMethode](javax.ejb.InvocationContext ctx) throws java.lang.Exception
Une classe interceptor ou une classe bean ne peut avoir qu’une seule méthode
@AroundInvoke.
Les interceptors sont une forme du pattern Chain of Responsibility.
Ce pattern concerne une chaîne d'objets qui sont liés à travers des références indirectes pour permettre à un objet de traiter une requête
ou la passer à un autre objet dans la chaîne.
C'est ce qui se passe avec les interceptors dans les EJB :
la méthode
proceed() provoque l'appel de la prochaine méthode interceptor dans la chaîne.
Quand la dernière méthode interceptor
AroundInvoke est appelée, la méthode
proceed() appelle la méthode métier du bean.
La flèche ci-dessous illustre les enchaînements dans le cas de plusieurs intercepteurs appliqués à un bean:
@Interceptors({Interceptor1.class, Interceptor2.class, Interceptor3.class})
public class MonBean {
…
public void methode() {
…}
}
Voici un exemple d'un mini projet qui définit une classe interceptor, ainsi qu'une méthode interceptor à l'intérieur du bean.
Les deux sont appelées mais selon un ordre:
les interceptors sont appelés dans l'ordre dans lequel ils sont spécifiés mais les interceptors définis dans des classes externes sont éxécutées
AVANT un interceptor définit dans une classe bean.
Dans cet exemple, elles affichent des traces dans les fichiers logs du serveur d'application, ainsi que dans le fichier de trace log4J.
Si on choisit le serveur d'application JBoss, il faut aller voir dans : {jboss-install-path}\server\default\log\server.log

La business interface BonjourRemote.java :
package org.developpez.tutorial.stateless.bean;
import javax.ejb.Remote;
@Remote
public interface BonjourRemote {
public void bonjour();
public String aurevoir();
}
Avec l'annotation
@Interceptors, on indique quelles classes externes vont être utilisées en tant qu'interceptors.
Toutes les méthodes du bean sont interceptées.
L'annotation
@AroundInvoke est utilisée pour définir une méthode en tant qu'interceptor.
La classe bean BonjourBean.java :
package org.developpez.tutorial.stateless.bean;
import javax.ejb.Stateless;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptors;
import javax.interceptor.InvocationContext;
import org.apache.log4j.Logger;
@Stateless
@Interceptors(org.developpez.tutorial.stateless.bean.TraceInterceptor.class)
public class BonjourBean implements BonjourRemote{
private static org.apache.log4j.Logger log = Logger.getLogger(BonjourBean.class);
public void bonjour()
{
log.info("Bonjour toi");
}
public String aurevoir()
{
return "au revoir";
}
@AroundInvoke
public Object myBeanInterceptor(InvocationContext ctx) throws Exception
{
if (ctx.getMethod().getName().equals("bonjour"))
{
log.info("*** bonjour!");
}
if (ctx.getMethod().getName().equals("aurevoir"))
{
log.info("*** Au revoir!");
}
return ctx.proceed();
}
}
La classe interceptor TraceInterceptor.java :
package org.developpez.tutorial.stateless.bean;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import org.apache.log4j.Logger;
public class TraceInterceptor {
private static org.apache.log4j.Logger log = Logger.getLogger(TraceInterceptor.class);
@AroundInvoke
public Object log(InvocationContext ctx) throws Exception
{
log.info("*** Interception par TraceInterceptor");
long start = System.currentTimeMillis();
try
{
return ctx.proceed();
}
catch(Exception e)
{
throw e;
}
finally
{
long time = System.currentTimeMillis() - start;
String method = ctx.getClass().getName() + "." + ctx.getMethod().getName() + "()";
log.info("*** TracingInterceptor : l'invocation de " + method + " a pris " + time + "ms");
}
}
}
La classe Client.java :
package org.developpez.tutorial.client;
import javax.naming.InitialContext;
import org.apache.log4j.Logger;
import org.developpez.tutorial.stateless.bean.BonjourRemote;
public class Client
{
private static org.apache.log4j.Logger log = Logger.getLogger(Client.class);
public static void main(String[] args) throws Exception
{
InitialContext ctx = new InitialContext();
BonjourRemote bonjourRemote = (BonjourRemote)ctx.lookup("BonjourBean/remote");
bonjourRemote.bonjour();
log.info(bonjourRemote.aurevoir());
System.out.println("Et voila !");
}
}
Le fichier jndi.properties :
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=localhost:1099
Le fichier log4j.properties :
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=sortie.log
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %d{hh:mm:ss} %t %c{1} -%m%n
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.MaxFileSize=100KB
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) -%m%n
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.rootLogger=stdout,R
log4j.appender.R.Append=true
En sortie, le fichier sortie.log (spécifique log4J) :
DEBUG 09:22:02 main SecurityAssociation -Using ThreadLocal: false
DEBUG 09:22:02 main Client -invoke called, but our invoker is disconnected, discarding and fetching another fresh invoker for: InvokerLocator [socket://192.168.1.111:3873/]
DEBUG 09:22:02 main SocketClientInvoker -connect called for: org.jboss.remoting.transport.socket.SocketClientInvoker@158f9d3
INFO 09:22:02 main Client -au revoir
En sortie, le fichier server.log (spécifique JBoss) :
2007-12-20 21:22:02,953 DEBUG [org.jboss.remoting.transport.socket.ServerThread] beginning dorun
2007-12-20 21:22:02,953 INFO [org.developpez.tutorial.stateless.bean.TraceInterceptor] *** Interception par TraceInterceptor
2007-12-20 21:22:02,953 INFO [org.developpez.tutorial.stateless.bean.BonjourBean] *** bonjour!
2007-12-20 21:22:02,953 INFO [org.developpez.tutorial.stateless.bean.BonjourBean] Bonjour toi
2007-12-20 21:22:02,953 INFO [org.developpez.tutorial.stateless.bean.TraceInterceptor] *** TracingInterceptor : l'invocation de org.jboss.ejb3.interceptor.InvocationContextImpl.bonjour() a pris 0ms
2007-12-20 21:22:02,968 INFO [org.developpez.tutorial.stateless.bean.TraceInterceptor] *** Interception par TraceInterceptor
2007-12-20 21:22:02,968 INFO [org.developpez.tutorial.stateless.bean.BonjourBean] *** Au revoir!
2007-12-20 21:22:02,968 INFO [org.developpez.tutorial.stateless.bean.TraceInterceptor] *** TracingInterceptor : l'invocation de org.jboss.ejb3.interceptor.InvocationContextImpl.aurevoir() a pris 0ms
On voit bien ici que la méthode
log(InvocationContext ctx) définit dans la classe interceptor est exécutée avant celle définit dans la classe bean,
myBeanInterceptor(InvocationContext ctx).
Cette technique d'interception se rapproche de la programmation par aspect (AOP): le code métier est séparé du code d'interception.
Une méthode interceptor est appelée un advice, et la classe contenant un advice est appelée un aspect.
On peut également appliquer une méthode interceptor à une seule méthode d'un bean, en annotant cette dernière :
@Interceptors(MaClass.class)
public void uneMethodeDuBean(int param){
...
}
On peut également définir les interceptors de façon déclarative dans le fichier de déploiement ejb-jar.xml :
Bjour
org.developpez.tutorial.stateless.bean.BonjourRemote
org.developpez.tutorial.stateless.bean.BonjourBean
...
TraceInterceptor
log
myBeanInterceptor
Je tiens à remercier Ricky81, OButterlin pour les conseils, remarques et relectures, ainsi que RideKick pour la correction orthographique.
Je remercie aussi www.developpez.com me permettant de publier cet article et Nono40 pour ses outils.
Copyright © Celinio FERNANDES. Aucune reproduction, même partielle, ne peut être
faite de ce site et de l'ensemble de son contenu : textes, documents, images,
etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi
jusqu'à 3 ans de prison et jusqu'à 300 000 Euros de dommages et intérets.