Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 9 di 68
  • livello ninja
Indice lezioni

Gli Interceptor

Gli interceptor sono utili nei casi in cui esiste un task comune a diverse classi e si vuole gestire il cambiamento in modo efficiente
Gli interceptor sono utili nei casi in cui esiste un task comune a diverse classi e si vuole gestire il cambiamento in modo efficiente
Link copiato negli appunti

Riutilizzo e manutenzione del software

La realizzazione di un software di qualità richiede che vengano rispettati diversi aspetti tra cui:

  • leggibilità
  • usabilità
  • riutilizzo
  • facilità di manutenzione
  • sicurezza

In questo capitolo desideriamo concentrarci sulle caratteristiche legate al riuso e alla facilità di manutenzione. Immaginate di gestire un progetto complesso dove sono stati realizzati diversi moduli. Il formato dei dati di input per i metodi delle classi è stato stabilito inizialmente fisso e non variabile e avete realizzato il software nel rispetto di tali requisiti.

Supponete ora che il committente vari i requisiti, esprimendo un'esigenza in base alla quale il formato dell'input da fisso diventa variabile, desiderando quindi che i moduli analizzino questo formato e consentano o meno il proseguimento in base alle informazioni lette. Quanto è manutenibile il software realizzato? Lo sforzo della manutenzione evolutiva è alto, dobbiamo modificare classi completate e testate con successo e ogni nuova modifica può introdurre bug. Siamo quindi costretti a replicare i test ed i test hanno
un costo.

Immaginate ancora che richieste di questo tipo si moltiplichino, la situazione può diventare estremamente difficile da gestire. Se però avessimo dei moduli che incapsulano la nuova logica di controllo e che si frappongano tra client e moduli esistenti, potremmo evitare la modifica del software dovendo applicare dei test soltanto alla nuova logica.

Interceptor Ejb 3

Gli Interceptor Ejb 3 risolvono questi problemi e sono estremamente utili per il riuso e la manutenzione. Si tratta essenzialmente di classi Java che contengono metodi
eseguiti prima della chiamata del metodo target dell'Ejb destinatario, ponendosi tra il client invocante e l'Ejb invocato.

Realizziamo adesso un esempio di Interceptor collegandolo al bean convertitore di temperatura versione Ejb 3.0 per semplicità di test con JUnit. Il nostro Interceptor si limita a stampare il valore dei parametri inviati al session bean. Definiamo la classe InterceptorLoggerBean:

package it.html.progetto1.interceptor.ejb30;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
public class InterceptorLoggerBean {
    @AroundInvoke
    public Object parametersLog(final InvocationContext context) throws Exception{
        System.out.println("Target bean:"+context.getTarget());
        System.out.println("Method:"+context.getMethod());
        for (Object parameter : context.getParameters()){
             System.out.println("Parameter:"+parameter);
        }
        return context.proceed();
    }
}

Un Interceptor è una normale classe Java che può contenere diversi metodi ma uno solo marcato con annotation @AroundInvoke. La definizione del metodo che vediamo
nel codice è esattamente quella che un metodo con annotation @AroundInvoke deve avere: prendere in input un oggetto che implementa l'interfaccia InvocationContext
e restituire un oggetto Object. L'InvocationContext ci consente di accedere ad una serie di informazioni per implementare la logica di intercettazione:

public interface InvocationContext {
  public Object getTarget();
  public Method getMethod();
  public Object[] getParameters();
  public void setParameters(Object[] newArgs);
  public Map<String,Object> getContextData();
  public Object proceed() throws Exception;
  public Object getTimer();
}

getTarget() restituisce un riferimento all'istanza del bean target. Ad esempio nel nostro caso ritornerà un Object che in realtà conterrà un oggetto it.html.progetto1.ejb30.ConverterBean.

Attraverso getMethod() possiamo recuperare un oggetto di riferimento per il metodo invocato correntemente sul bean, mentre getParameters() e setParameters() permettono di agire sui parametri di input del metodo invocato.

Il metodo getContextData() consente di passare dati tra Interceptor, possiamo infatti definire una catena di Interceptor che conduce all'invocazione di un metodo del bean, mentre
proceed() autorizza ad andare avanti nella catena degli Interceptor; nel nostro caso la catena è costituita da un solo Interceptor, per cui proceed() autorizzerà l'invocazione del metodo del bean.

getTimer(), infine, ritorna il timer associato con il timeout sull'invocazione del metodo.

Un Interceptor può essere collegato alla classe del bean affinchè venga attivato sulla chiamata di tutti i metodi del bean o su uno o più metodi del bean. Per collegare un Interceptor a livello di classe o di metodo ad un session bean facciamo uso dell'annotazione @Interceptors nella classe del bean; vediamo quindi come utilizzare l'InterceptorLoggerBean in modo tale che si attivi su tutti i metodi del ConverterBean:

package it.html.progetto1.ejb30;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.interceptor.Interceptors;
import it.html.progetto1.interceptor.ejb30.InterceptorLoggerBean;
/**
 * Session Bean implementation class ConverterBean
 */
@Stateless(name="ConverterBean", mappedName = "ConverterBean")
@Interceptors(InterceptorLoggerBean.class)
public class ConverterBean implements ConverterBeanRemote, ConverterBeanLocal {
    /**
     * Default constructor.
     */
    public ConverterBean() {
        // TODO Auto-generated constructor stub
    }
    @Override
    @TransactionAttribute(TransactionAttributeType.NEVER)
    public float celsiusToFahrenheit(float temperature) {
          return temperature * 1.8f + 32;
    }
    @Override
    @TransactionAttribute(TransactionAttributeType.NEVER)
    public float fahrenheitToCelsius(float temperature) {
         return (temperature - 32) / 1.8f;
    }
}

Tutto quello che dobbiamo fare è utilizzare l'annotation @Interceptors specificando la classe (o le classi) dell'Interceptor che intendiamo utilizzare; non dobbiamo applicare modifiche alla classe di test e i casi di test definiti consentiranno anche di comprendere il corretto funzionamento dell'Interceptor.

Catene di Interceptor

Vediamo ora qualche concetto per un utilizzo avanzato. Come detto in precedenza, possiamo realizzare catene di Interceptor da attivare in sequenza sulla chiamata ai metodi di un bean. Questo è reso possibile dall'annotation Interceptors che prende, in generale, un array di classi:

@Stateless
@Interceptors({FirsInterceptor.class, SecondInterceptor.class,...})
public class MySessionBean { ... }

Oppure per un Interceptor a livello di metodo:

@Stateless
public class MySessionBean {
 @Interceptors({FirsInterceptor.class, SecondInterceptor.class,...})
 public void myMethod(){....}
}

L'ordine di invocazione degli Interceptor è esattamente quello definito nell'annotation. I metodi della classe Interceptor annotati con @AroundInvoke
possono avere un accesso di tipo public, private, protected o a livello di package e non devono essere dichiarati static o final.

All'interno della classe dell'Interceptor possiamo utilizzare l'annotation @AroundConstruct su metodi che desideriamo si interpongano verso la chiamata del costruttore del bean. Il metodo della classe Interceptor annotato con @AroundConstruct è invocato dopo che la dependency injection è stata completata per tutti gli Interceptor della classe bean target. L'istanza della classe bean target è quindi creata dopo che tutti i metodi con annotation @AroundConstruct hanno invocato Invocation.proceed().

Possiamo definire degli Interceptor anche per i metodi di timeout dei Timer utilizzando l'annotazione @AroundTimeout, ma è consentito soltanto un metodo con annotation @AroundTimeout per classe:

public class MyInterceptorClass {
 @AroundTimeout
 public void timeoutInterceptorMethod(InvocationContext context) { ... }
}

I metodi della classe Interceptor per i timeout dei Timer possono avere un accesso public, private, protected o a livello di package e non devono essere dichiarati static o final. L'Interceptor, infine, può accedere all'oggetto Timer associato con il metodo di timeout attraverso l'istanza di InvocationContext con getTimer().

Ti consigliamo anche