@AroundInvoke
, dans sa propre classe ou dans une de ses super classes. Cette méthode ne peut être ni
static
ni
final
, en revanche elle peut être indifféremment
private
,
protected
ou
public
. Cette méthode doit avoir la signature suivante.
Exemple 72. Exemple d'intercepteur
public class LoggingInterceptor { @AroundInvoke private Object checArguments(InvocationContext context) { // corps de la méthode } // reste de la classe }
@PostConstruct
et
@PostActivate
pour la construction et l'activation, ainsi que
@PreDestroy
et
@PreActivate
pour l'effacement et la passivation.
Ces méthodes annotées doivent prendre l'objet
InvocationContext
en paramètre, et invoquer sa méthode
proceed()
pour que les autres méthodes annotées de la même manière, que ce soit dans d'autres intercepteurs ou dans l'EJB final soient également appelées.
getContextData()
: retourne une
Map
qui porte les données associées au contexte d'invocation.
getMethod()
: retourne une référence sur la méthode interceptée.
getParameters()
: retourne un tableau d'
Object
qui porte les paramètres passés à cette méthode.
getTarget()
: retourne une référence sur l'instance de l'EJB intercepté.
setParameters(Object [])
: l'appel à cette méthode permet de modifier les paramètres envoyés à la méthode interceptée.
proceed()
: cette méthode doit être appelée pour continuer l'exécution normale du processus d'interception.
proceed()
a un statut particulier. C'est le fait de l'invoquer qui permet d'invoquer la méthode interceptée, ou l'intercepteur suivant s'il y en a un. Donc, l'intercepteur dans lequel on se trouve a le choix, de poursuivre le processus d'exécution nominal, et d'exécuter la méthode métier, ou de l'interrompre et donc d'empêcher son exécution.
Une méthode d'interception suit donc le modèle suivant.
Exemple 73. Écriture d'un intercepteur
public class SecurityInterceptor { // méthode appelée par l'interception @AroundInvoke private Object enforce(InvocationContext context) throws Exception { // la méthode validate vérifie que le context // répond bien aux exigences de sécurité if (validate(context)) { // cet appel invoque la méthode métier interceptée Object returnedObject = context.proceed() ; // on récupère l'objet retourné, que l'on pourrait modifier return returnedObject ; } else { // on décide ici de ne pas appeler la méthode métier finale, // puisque la méthode valide() a retourné false Method method = context.getMethod() ; Class<?> returnedType = method.getReturnType() ; Object returnedObject = getDefaultInstance(returnedType) ; return returnedObject ; } } }
@Interceptors
sur la classe de cet EJB, ou sur une méthode particulière. On peut déclarer plusieurs intercepteurs, auquel cas on les déclare dans un tableau, attribut de
@Interceptors
. Voyons ceci sur un exemple.
Exemple 74. Déclaration d'intercepteurs sur un EJB
@Interceptors({ SecurityInterceptor.class, LoggingInterceptor.class }) @Stateless(mappedName="MarinService") @Remote(MarinService.class) public class MarinServiceImpl implements MarinService { @Interceptors(ValidationInterceptor.class) @Override public long createMarin(String nom) { // corps de la méthode } // reste de la classe }
@AroundInvoke
. Lors de l'appel à la méthode
createMarin(String)
, le serveur d'application se rend compte que trois intercepteurs sont définis. Il va donc les invoquer, dans l'ordre dans lequel ils sont écrits, en commençant pas les intercepteurs définis sur la classe. Dans notre exemple, cet ordre sera donc :
SecurityInterceptor.class
,
LoggingInterceptor.class
et
ValidationInterceptor.class
.
Pour chacun de ces intercepteurs, il va invoquer la méthode annotée par
@AroundInvoke
. Il passera en paramètre de cette méthode un objet de type
InvocationContext
construit à partir des informations de la méthode interceptée.
createMarin(String)
. Nous allons créer un annotation
@StringNotNull
, qui, une fois posée sur notre méthode, permettra à un intercepteur de tester si le paramètre est nul ou pas, et s'il l'est, de ne pas appeler cette méthode.
Exemple 75. Annotation
@StringNotNull
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface StringNotNull { }
@interface
(notons le caractère
@
avant le mot-clé
interface
). Une annotation est un élément ajouté au
byte code
d'une classe, qui peut être posé sur différents éléments d'une classe, tels que la classe elle-même, ses champs, ses constructeurs, ses méthodes, ou encore les paramètres de ses méthodes. Ici, cette annotation est présente dans le
byte code
, et chargée par la JVM. Elle peut être posée sur les paramètres d'une méthode.
MarinServiceImpl
pour utiliser cette annotation.
Exemple 76. Utilisation de l'annotation
@StringNotNull
@Stateless(mappedName="MarinService") @Remote(MarinService.class) public class MarinServiceImpl implements MarinService { @PersistenceContext(unitName="cours-ear-pu") private EntityManager em ; @Override public long createMarin(@StringNotNull String nom) { Marin marin = new Marin() ; marin.setNom(nom) ; em.persist(marin) ; return marin.getId() ; } }
@StringNotNull
, et d'appliquer la règle de non-nullité. Écrivons cet intercepteur.
Exemple 77. Intercepteur associé à
@StringNotNull
public class StringNotNullInterceptor { // méthode permettant de retourner une valeur par // défaut à partir d'un type private Object getDefaultValue(Class<?> type) { // cette méthode retourne true si le type // correspond en fait à un type primitif Java if (type.isPrimitive()) { if (byte.class.equals(type)) { return 0 ; } else if (short.class.equals(type)) { return 0 ; } else if (int.class.equals(type)) { return 0 ; } // etc... ne sont pas traités : long, boolean, char, // float, double } return null ; } @AroundInvoke private Object validateStringNotNull(InvocationContext context) throws Exception { Method method = context.getMethod() ; // lecture des types des paramètres de la méthode interceptée Class<?>[] parameterTypes = method.getParameterTypes() ; for (int index = 0 ; index < parameterTypes.length ; index++) { // analyse des paramètres de type String if (String.class.equals(parameterTypes[index])) { // lecture des annotations de ce paramètre Annotation[] annotations = method.getParameterAnnotations()[index] ; for (Annotation annotation : annotations) { // l'annotation StringNotNull existe-t-elle ? if (StringNotNull.class.equals(annotation.annotationType())) { // ici l'on regarde un paramètre de type String, // annoté par StringNotNull String stringParameter = (String)context.getParameters()[index] ; if (stringParameter == null) { // on a un paramètre de type String, annoté, et null // on ne doit donc pas appeler la méthode Object returnedObject = getDefaultValue(method.getReturnType()) ; return returnedObject ; } else { // ici le paramètre est non nul, on continue donc // à explorer les autres paramètres de la méthode } } } } } // si nous sommes arrivés ici, c'est qu'aucun paramètre de type String // annoté et nul n'a été trouvé, on peut donc appeler la méthode // interceptée return context.proceed() ; } }
Exemple 78. Interception finale de l'EJB
MarinServiceImpl
@Interceptors({ StringNotNullInterceptor.class, }) @Stateless(mappedName="MarinService") @Remote(MarinService.class) public class MarinServiceImpl implements MarinService { // reste de la classe }