Intercepteurs EJB 3.x
Un intercepteur est une méthode qui est automatiquement appelée lorsque les méthodes métier d'un Enterprise JavaBeans (EJB) sont invoquées ou lorsque des événements particulières du cycle de vie de l'EJB se produisent.
Il existe trois types de méthodes interceptrices : les intercepteurs pour méthodes métier, les intercepteurs pour méthodes timeout (nouveauté dans EJB 3.1) et les intercepteurs pour rappels d'événement de cycle de vie. Les intercepteurs pour méthodes métier sont invoqués autour d'un appel à une méthode métier. Les intercepteurs pour méthodes timeout sont invoqués autour d'un appel à une méthode ejbTimeout. Les intercepteurs pour rappels d'événement de cycle de vie sont appelés autour de l'un des événements de cycle de vie PostConstruct, PreDestroy, PrePassivate ou PostActivate. Une classe particulière ne peut déclarer qu'une seule méthode interceptrice de chaque type. Toutefois, chaque classe dans une hiérarchie peut déclarer une méthode interceptrice de chaque type. Si une méthode interceptrice dans une sous-classe redéfinit la même méthode dans une superclasse, seule la méthode dans la sous-classe pourra être invoquée.
Une méthode interceptrice est autorisée à accéder et à appeler l'ensemble des ressources et des composants que la méthode associée est elle-même autorisée à appeler. De même, une méthode interceptrice s'exécute avec le même contexte transactionnel et de sécurité que celui de la méthode associée. Excepté dans le cas des beans de session singleton, les méthodes interceptrices pour rappels d'événement de cycle de vie sont exécutées avec le confinement de transaction locale (LTC, local transaction containment).
Vous pouvez déclarer des méthodes interceptrices directement dans la classe d'EJB concernée ou dans une classe d'intercepteur à part. Si vous optez pour cette seconde solution, vous devez lier la classe d'intercepteur à l'EJB en utilisant soit une annotation, soit un code XML. L'exemple suivant montre comment déclarer des intercepteurs en utilisant des annotations :
@Interceptors({ClassInterceptor1.class, ClassInterceptor2.class})
public class TestBean { /* ... */ }
@Interceptors({ClassInterceptor1.class})
public class TestBean2 {
@Interceptors({MethodInterceptor1.class, MethodInterceptor2.class})
public void businessMethod() { /* ... */ }
}
L'exemple suivant montre comment déclarer des intercepteurs en utilisant le descripteur de déploiement (code XML) :
<assembly-descriptor>
<interceptor-binding>
<ejb-name>TestBean</ejb-name>
<interceptor-class>ClassInterceptor1</interceptor-class>
<interceptor-class>ClassInterceptor2</interceptor-class>
</interceptor-binding>
<interceptor-binding>
<ejb-name>TestBean2</ejb-name>
<interceptor-class>ClassInterceptor1</interceptor-class>
</interceptor-binding>
<interceptor-binding>
<ejb-name>TestBean2</ejb-name>
<interceptor-class>MethodInterceptor1</interceptor-class>
<interceptor-class>MethodInterceptor2</interceptor-class>
<method>
<method-name>businessMethod</method-name>
</method>
</interceptor-binding>
</assembly-descriptor>
Vous pouvez exclure des intercepteurs de niveau classe d'une méthode particulière en utilisant soit l'annotation ExcludeClassInterceptors, soit l'élément équivalent, exclude-class-interceptors, dans le descripteur de déploiement. Dans l'exemple suivant, l'intercepteur ClassInterceptor est exclu de la méthode businessMethod.
@Interceptors({ClassInterceptor1.class})
public class TestBean2 {
@ExcludeClassInterceptors
public void businessMethod() { /* ... */ }
public void businessMethodWithClassInterceptor1() { /* ... */
}
L'exemple suivant montre comment exclure l'intercepteur de la méthode en utilisant cette fois le descripteur de déploiement :
<assembly-descriptor>
<interceptor-binding>
<ejb-name>TestBean2</ejb-name>
<exclude-class-interceptors>true</exclude-class-interceptors>
<method>
<method-name>businessMethod</method-name>
</method>
</interceptor-binding>
</assembly-descriptor>
Les méthodes interceptrices peuvent avoir une visibilité publique, protégée, privée au sein du package ou privée. Elles ne doivent pas être finales ni statiques. Les intercepteurs pour méthodes métier, ainsi que les intercepteurs pour méthodes timeout, doivent avoir un type de retour java.lang.Object. Ils ne doivent prendre qu'un seul paramètre (un javax.interceptor.InvocationContext) et ne doivent avoir qu'une seule clause throws pour lancer une java.lang.Exception. Quant aux intercepteurs pour rappels d'événement du cycle de vie, ils doivent avoir un type de retour void et ne doivent pas comporter de clause throws. Ceux qui sont déclarés directement dans la classe de l'EJB ne doivent prendre aucun paramètre ; ceux qui sont déclarés dans une superclasse de la classe d'EJB ou dans une classe d'intercepteur à part doivent prendre un seul paramètre, un javax.interceptor.InvocationContext. Les intercepteurs pour méthodes timeout, de même que les intercepteurs pour rappels d'événement du cycle de vie, ne doivent pas lancer d'exception d'application.
Vous pouvez utiliser le paramètre InvocationContext d'une méthode interceptrice pour obtenir des informations sur la méthode invoquée. La méthode getTarget renvoie l'instance de bean invoquée. La méthode getTimer s'applique uniquement aux méthodes timeout ; elle renvoie le temporisateur (timer) exécuté. La méthode getMethod renvoie la méthode d'interface métier invoquée. La méthode getParameters renvoie les paramètres qui sont passés à la méthode métier, tandis que la méthode setParameters permet de modifier ces paramètres. La méthode getContextData renvoie l'association de données avec la méthode invoquée. Enfin, la méthode proceed invoque soit l'intercepteur suivant, soit la méthode cible.
Vous pouvez déclarer des méthodes interceptrices en utilisant soit des annotations, soit du XML. Pour déclarer une méthode interceptrice avec une annotation, placez l'annotation appropriée, AroundInvoke, AroundTimeout, PostConstruct, PreDestroy, PrePassivate ou PostActivate, sur la méthode interceptrice. L'exemple suivant montre comment déclarer, à l'aide d'annotations, un intercepteur pour méthodes métier, un intercepteur pour méthodes timeout et un intercepteur pour rappels d'événement de cycle de vie PostConstruct sur une classe d'EJB.
@Interceptors({ClassInterceptor.class})
public class TestBean {
@PostConstruct
private void beanPostConstruct() { /* ... */ }
@AroundInvoke
protected Object beanAroundInvoke(InvocationContext ic) throws Exception {
return ic.proceed();
}
@AroundTimeout
protected Object beanAroundTimeout(InvocationContext ic) throws Exception {
return ic.proceed();
}
}
L'exemple suivant montre comment déclarer les mêmes intercepteurs sur une classe d'intercepteur à part.
public class ClassInterceptor {
@PostConstruct
private void interceptorPostConstruct(InvocationContext ic) {
try {
ic.proceed();
} catch (Exception ex) { /* ... */ }
}
@AroundInvoke
protected Object interceptorAroundInvoke(InvocationContext ic) throws Exception {
return ic.proceed();
}
@AroundTimeout
protected Object interceptorAroundTimeout(InvocationContext ic) throws Exception {
return ic.proceed();
}
}
Une autre solution consiste à déclarer les méthodes interceptrices dans le descripteur de déploiement, avec les éléments appropriés : around-invoke, around-timeout, post-construct, pre-destroy, pre-passivate et post-activate. L'exemple suivant montre comment déclarer, à l'aide du descripteur de déploiement, un intercepteur pour méthodes métier, un intercepteur pour méthodes timeout et un intercepteur pour rappels d'événement de cycle de vie PostConstruct sur une classe d'EJB et sur une classe d'intercepteur à part.
<enterprise-beans>
<session>
<ejb-name>TestBean</ejb-name>
<around-invoke>
<method-name>beanAroundInvoke</method-name>
</around-invoke>
<around-timeout>
<method-name>beanAroundTimeout</method-name>
</around-timeout>
<post-construct>
<lifecycle-callback-method>beanPostConstruct</lifecycle-callback-method>
</post-construct>
</session>
</enterprise-beans>
<interceptors>
<interceptor>
<interceptor-class>ClassInterceptor</interceptor-class>
<around-invoke>
<method-name>interceptorAroundInvoke</method-name>
</around-invoke>
<around-timeout>
<method-name>interceptorAroundTimeout</method-name>
</around-timeout>
<post-construct>
<lifecycle-callback-method>interceptorPostConstruct</lifecycle-callback-method>
</post-construct>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>TestBean</ejb-name>
<interceptor-class>ClassInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
Vous pouvez également déclarer des méthodes interceptrices sur les superclasses. L'exemple suivant montre comment déclarer un intercepteur PostActivate sur une superclasse d'un bean en utilisant des annotations :
public class TestBean extends BeanSuperClass { /* ... */ }
public class BeanSuperClass {
@PostActivate
private void beanSuperClassPostActivate(InterceptorContext ic) {
try {
ic.proceed();
} catch (Exception ex) { /* ... */ }
}
}
L'exemple suivant montre comment déclarer, toujours à l'aide d'annotations, le même intercepteur sur une superclasse d'une classe d'intercepteur à part.
public class ClassInterceptor extends InterceptorSuperClass { /* ... */ }
public class InterceptorSuperClass {
@PostActivate
private void interceptorSuperClassPostActivate(InterceptorContext ic) {
try {
ic.proceed();
} catch (Exception ex) { /* ... */ }
}
}
Vous pouvez déclarer les mêmes méthodes interceptrices en utilisant le descripteur de déploiement. L'exemple suivant montre comment déclarer une méthode interceptrice sur la superclasse d'un bean et sur la superclasse d'une classe d'intercepteur à part :
<enterprise-beans>
<session>
<ejb-name>TestBean</ejb-name>
<post-activate>
<class>BeanSuperClass</class>
<lifecycle-callback-method>beanSuperClassPostActivate</lifecycle-callback-method>
</post-activate>
</session>
</enterprise-beans>
<interceptors>
<interceptor>
<interceptor-class>ClassInterceptor</interceptor-class>
<post-activate>
<class>InterceptorSuperClass</class>
<lifecycle-callback-method>interceptorSuperClassPostActivate</lifecycle-callback-method>
</post-activate>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>TestBean</ejb-name>
<interceptor-class>ClassInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
Vous pouvez déclarer des intercepteurs par défaut, à appliquer à tous les beans de session et de message (MDB) dans un module. Les intercepteurs par défaut ne peuvent être déclarés que dans le descripteur de déploiement et sont spécifiés avec l'ejb-name "*". L'exemple suivant montre comment déclarer un intercepteur par défaut.
<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>DefaultInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
Vous pouvez exclure les intercepteurs par défaut d'une classe ou d'une méthode spécifiques en utilisant soit l'annotation ExcludeDefaultInterceptors, soit l'élément XML exclude-default-interceptors dans le descripteur de déploiement. L'exemple suivant montre comment exclure les intercepteurs par défaut en utilisant l'annotation prévue à cet effet :
@ExcludeDefaultInterceptors
public class TestBean { /* ... */ }
public class TestBean2 {
@ExcludeDefaultInterceptors
public void businessMethod() { /* ... */ }
}
L'exemple suivant montre comment exclure les intercepteurs par défaut en utilisant l'élément XML prévu à cet effet dans le descripteur de déploiement :
<assembly-descriptor>
<interceptor-binding>
<ejb-name>TestBean</ejb-name>
<exclude-default-interceptors>true</exclude-default-interceptors>
</interceptor-binding>
<interceptor-binding>
<ejb-name>TestBean2</ejb-name>
<exclude-default-interceptors>true</exclude-default-interceptors>
<method>
<method-name>businessMethod</method-name>
</method>
</interceptor-binding>
</assembly-descriptor>
Lorsque les intercepteurs sont invoqués pour une méthode, les premiers appelés sont ceux des classes d'intercepteur par défaut. Viennent ensuite les intercepteurs de niveau classe et enfin les méthodes interceptrices de la classe d'EJB. Pour une même hiérarchie de classes d'intercepteur, les méthodes interceptrices sont toujours invoquées sur la superclasse la plus générale en premier. Les intercepteurs par défaut et de niveau classe sont invoqués suivant l'ordre spécifié dans le descripteur de déploiement ou par l'annotation Interceptors. Vous pouvez imposer un ordre différent en spécifiant la liste complète des intercepteurs par défaut et de niveau classe dans l'élément interceptor-order du descripteur de déploiement.
<assembly-descriptor>
<interceptor-binding>
<ejb-name>TestBean</ejb-name>
<!--
L'ordre par défaut serait le suivant :
1. DefaultInterceptor
2. ClassInterceptor1
3. ClassInterceptor2
La section suivante permet de spécifier un ordre différent de l'ordre par défaut.
-->
<interceptor-order>
<interceptor-class>ClassInterceptor2</interceptor-class>
<interceptor-class>DefaultInterceptor</interceptor-class>
<interceptor-class>ClassInterceptor1</interceptor-class>
</interceptor-order>
</interceptor-binding>
</assembly-descriptor>