CERCA SITEMAP FEED RSS 1280
Ultimo aggiornamento: 30 Agosto 2009

Interceptors e Aspect-Oriented Programming (AOP)

Quando esiste un task comune a più classi java e si vuole evitare di modificare a seguito di cambiamenti ogni singola classe si possono utilizzare gli interceptors.
Un tipico esempio è rappresentato dall’operazione di logging che spesso è richiesta per accedere a diversi servizi offerti.
Se le informazioni di logging cambiano e l’operazione di logging è stata implementata separatamente per ciascuna classe è necessario modificare una ad una tutte le classi.
E’ più conveniente definire un interceptor che si occupa di effettuare l’operazione di logging e che viene attivato di default quando il metodo di un bean viene eseguito.
L’idea dietro Aspect-Oriented Programming (AOP) è rappresentata dal fatto che molte applicazioni fanno uso spesso di moduli (come quello di logging) che sebbene non costituiscano la logica di business dell’applicazione vengono spesso utilizzati.
Un sistema AOP effettua una separazione fra tali moduli e il livello logivo eseguendo ad esempio il codice in essi contenuto all’inizio di ogni invocazione di un metodo.

Interceptor

Enterprise JavaBeans 3 consente di definire degli interceptor che vengono attivati quando il metodo di un EJB viene invocato.
L’EJB object, il proxy dell’EJB, è un interceptor che fornisce diverse funzionalità messe a disposizione dal container.
Gli interceptor possono essere piazzati all’inizio di un metodo: nel nostro esempio l’interceptor è attaccato al metodo accoda e stamperà un messaggio di log ad ogni invocazione dello stesso.
@Stateless
public class SessionBeanProducer implements SessionBeanProducerLocal 
{

  ...
  
  public SessionBeanProducer() 
  {
  }

  @Override
  @Interceptors(MioInterceptor.class)
  public void accoda(String comando) 
  {
   
   ...
     
  }

}
package miopackage;

import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

public class MioInterceptor 
{
  @AroundInvoke
  public Object metodoLog(InvocationContext invocationcontext) throws Exception
  {
    System.out.println("Interceptor - Invocato metodo: "+invocationcontext.getMethod().getName());
    return invocationcontext.proceed();
    
  }
}
La classe MioInterceptor è attaccata al metodo accoda mediante l’annotazione javax.interceptor.Interceptors, e il metodo metodoLog annotato con javax.interceptor.AroundInvoke stampa il messaggio di log includendo il nome del metodo che lo ha attivato sfruttando il metodo getMethod().getName() di javax.interceptor.InvocationContext.
Infine viene invocato il metodo proceed così che l’elaborazione del metodo accoda possa procedere normalmente.

Specificare gli interceptor

L’annotazione @Interceptors viene utilizzata per specificare una o più classi interceptor per il metodo di una classe.

I nomi delle classi vanno separati mediante l’uso della virgola:
@Interceptors({Interceptor1.class, Interceptor2.class})
public void accoda(String comando)
{ 
  ... 
}
E’ possibile anche definire un interceptor a livello di classe che viene applicato a tutti i metodi della classe
@Interceptors(MioInterceptor.class)
@Stateless
public class MioBean implements MiaInterfacciaBean 
{
  public void metodo1()
  {
    ...
  }

  public void metodo2()
  {
    ...
  }
}
Se si vuole evitare di specificare un interceptor a livello classe o metodo e si vuole definire un interceptor di default è possibile farlo configurando opportunamente il deployment descriptor:
<assembly-descriptor>
  <interceptor-binding>
    <ejb-name>*</ejb-name>
    <interceptor-class>
      mippackage.MioInterceptor
    </interceptor-class>
  </interceptor-binding>
</assembly-descriptor>
In questo caso MioInterceptor è l’interceptor di default dell’applicazione.
Se sono stati definiti più interceptor a livelli differenti questi vengono invocati in ordine, dal più generico al più specifico (default, classe, metodo).
E’ possibile comunque alterare l’ordine di esecuzione degli interceptor mediante l’elemento interceptor-order del deployment descriptor.
Utilizzando le annotazioni è invece possibile disabilitare gli interceptor di default o gli interceptor di classe su un particolare metodo usando rispettivamente @javax.interceptor.ExcludeDefaultInterceptors e @javax.interceptor.ExcludeClassInterceptors.
@ExcludeClassInterceptors
public void metodo()
{
  ...
}

Annotazione @AroundInvoke

Un interceptor deve avere sempre e solo un unico metodo annotato con @AroundInvoke che non deve essere un metodo business (non deve essere un metodo publico che appartiene all’interfaccia business del bean).
Tale metodo viene automaticamente attivato dal container quando il metodo del bean viene invocato.
@AroundInvoke
public Object metodoLog(InvocationContext invocationcontext) throws Exception
{
  System.out.println("Interceptor - Invocato metodo: "+invocationcontext.getMethod().getName());
  return invocationcontext.proceed();  
}
L’interfaccia InvocationContext passata come parametro del metodo fornisce importanti informazioni: ad esempio getMethod().getName() restituisce il nome del metodo che è stato intercettato.
Se alla fine si vuole proseguire con l’esecuzione del metodo invocato occorre restituire l’oggetto ritornato da invocationContext.proceed() altrimenti se qualcosa va male si può generare un’eccezione e il metodo invocato non viene eseguito:
@AroundInvoke
public Object metodoLog(InvocationContext invocationcontext) throws Exception
{
  System.out.println("Interceptor - Invocato metodo: "+invocationcontext.getMethod().getName());

  if (problema) 
  {  
    throw new SecurityException("Problema di sicurezza");
  }

  return invocationcontext.proceed();  
}

L'interfaccia InvocationContext

L’intefaccia InvocationContext ha diversi metodi utili:
public interface InvocationContext 
{
  public Object getTarget();
  public Method getMethod();
  public Object[] getParameters();
  public void setParameters(Object[]);
  public java.util.Map<String,Object> getContextData();
  public Object proceed() throws Exception;
}
  • getTarget recupera l’istanza del bean il cui metodo è stato intercettato.
  • getMethod restituisce il metodo del bean che è stato intercettato.
  • getParameters restituisce un array dei parametri passati al metodo intercettato
  • setParameters consente di cambiare il valore di tali parametri prima che il metodo venga attivato
  • getContextData può essere utilizzato per la comunicazione fra interceptor (quando questi formano una catena) perché il context è condiviso fra questi.
Quindi ad esempio un interceptor potrebbe inserire una coppia nome valore
invocationContext.getContextData().put("stato", "Sconosciuto");
e il successivo interceptor nella catena recuperare tale valore
String stato = (String) invocationContext.getContextData().get("stato");

Monitorare il ciclo di vita di un Interceptor

Le annotazioni @PostConstruct, @PrePassivate, @PostActivate e @PreDestroy possono essere applicate ai metodi di una classe interceptor definendo i cosidetti lifecycle callback interceptors.
public class MioInterceptor 
{
  @PostConstruct
  public void inizializza(InvocationContext context) 
  {
    ... allocazione risorse
    context.proceed();
  }

  @PreDestroy
  public void rilascia (InvocationContext context) 
  {
    ... deallocazione risorse
    context.proceed();
  }

}