@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 }