Filtri e Listener sono due componenti molto utili e potenti della Servlet API. Analizziamone l‘utilizzo nell‘ambito dello sviluppo di una applicazione web.
Introduzione
Tra i vari elementi che costituiscono la Servlet API i filtri e i listener sono senza dubbio molto interessanti e potenti per le varie applicazioni che possono trovare nell‘utilizzo pratico. In questo articolo descriviamo cosa sono e per quali scopi possano essere utilizzati nello sviluppo di una applicazione web.
I filtri
I Filtri sono senza dubbio uno dei componenti più utili e versatili nell‘ambito della Servlet API. Essi consentono di aggiungere funzionalità specifiche all‘intera applicazione web senza dover mettere mano al codice delle servlet che la compongono. Queste funzionalità possono essere aggiunte o eliminate in maniera indipendente l‘una dall‘altra e con una semplice configurazione esterna al codice. Un filtro è una classe in grado di intercettare una richiesta e eseguire elaborazioni sulla richiesta stessa o sulla risposta generata da una servlet. I filtri vengono configurati nel deployment descriptor dell‘applicazione per intercettare tutte le richieste client ad un determinata servlet o a un gruppo di servlet individuate da uno specifico URL pattern; in questo modo l‘elaborazione della richiesta da parte di un filtro avviene prima che questa arrivi alla servlet a cui era destinata la richiesta stessa. Più filtri possono essere configurati per essere eseguiti in cascata e aggiungere in maniera perfettamente modulare ciascuno la sua elaborazione alla stessa richiesta, arricchendo così tutta l‘applicazione web di funzionalità che altrimenti richiederebbero l‘intervento in ogni singola servlet.
Tipicamente con i filtri è possibile elaborare le richieste per eseguire operazioni quali l‘esecuzione di controlli di sicurezza, l‘esecuzione di operazioni di logging dell‘applicazione, elaborazioni particolari sull‘header o il corpo della richiesta stessa.
I filtri possono anche agire sulla risposta ad esempio per effettuare una compressione della stessa per aumentare le performance.
Una classe per essere un filtro deve implementare l‘interfaccia standard javax.servlet.Filter che prevede tre metodi:
public void init()
È il metodo invocato dal web container per inizializzare il filtro
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
È il metodo invocato dal web container per eseguire l‘elaborazione del filtro quando questo è invocato nella catena di filtri
public void destroy()
È il metodo invocato dal web container per mettere fuori servizio il filtro
Il filtro ha un ciclo di vita molti simile a quello di una normale servlet, e il metodo doFilter() è l‘analogo dei metodi doGet() e doPost(). Nel doFilter() lo sviluppatore può inserire il codice relativo alle operazioni che vuole fare eseguire al filtro sulla richiesta o sulla risposta che vengono passate in input al metodo e che al termine della catena di filtri giungeranno alla servlet alla quale era diretta la richiesta.
I filtri utilizzano un oggetto che implementa l‘interfaccia javax.servlet.FilterChain passato in input al metodo doFilter() per invocare il filtro seguente nella catena o la servlet se il filtro è l‘ultimo della catena.
La determinazione della sequenza dei filtri della catena è fatta nel deployment descriptor dell‘applicazione, e definire un filtro è molto simile alla definizione di una servlet. Di seguito è riportata la sezione di un web.xml di una applicazione web nella quale viene definito un filtro per il logging dell‘applicazione LoggingFilter che va in esecuzione ad ogni richiesta mappata sul url pattern *.do. Questo è un esempio di come si può aggiungere una funzionalità di impatto sull‘intera applicazione quale il logging di tutte le richieste senza intervenire nel codice ma realizzando un filtro ad hoc e configurandolo esternamente al codice.
LoggingFilter
it.mokabyte..LoggingFilter
LoggingFilter
action
org.apache.struts.action.ActionServlet
config
/WEB-INF/struts-config.xml
1
action
*.do
Come indicato nella documentazione della Servlet API [5] i filtri sono particolarmente adatti per operazioni quali:
- Autenticazione
- Logging e Auditing
- Conversione di immagini
- Compressione dati
- Encryption
- Tokenizing
- Trigger per eventi legati all‘accesso a risorse
- Trasformazioni XSLT
- Mime-type chain
In particolare è molto interessante il punto 4 che ci fa capire come con i filtri si possa intervenire anche sulla risposta di una servlet. La tecnica utilizzata è quella di passare dal filtro alla servlet una request “wrappata” da una classe che consente poi di agire sull‘output strema generato dalla servlet stessa. In [1] c‘è un ottimo esempio di implementazione di questa tecnica basata sul pattern Decorator; il pattern Decorator fornisce una soluzione per inglobare una classe con un wrapper in modo da delegare a questa le funzionalità che si vuole lasciare così come sono ma implementando funzionalità nuove per estendere il funzionamento della classe originaria. In [7] sono spiegati molto bene due utilissimi filtri per applicazioni web tra i quali proprio quello che consente di comprimere l‘output di una servlet.
Un Filtro di esempio
A titolo di esempio riportiamo di seguito il codice completo di un filtro per dare l‘idea della semplcità di realizzazione ma allo stesso tempo della potenza di questo strumento. Nella scrittura di questo filtro si fa l‘ipotesi che l‘applicazione web memorizzi in sessione un oggetto it.mokabyte.Utente per un utente che esegue il login. Si suppone inoltre che l‘applicazione utilizzi il Log4J per il logging. Il filtro si propone di aggiungere ad ogni richiesta che proviene all‘applicazione l‘informazione relativa all‘utente e all‘host remoto dal quale essa proviene. Il filtro utilizza la classe org.apache.log4j.NDC [4] per aggiungere al thread corrente le informazioni desiderate.
package it.mokabyte.filters;
import it.mokabyte.Utente;
import org.apache.log4j.NDC;
import java.io.IOException;
/**
* La classe è un filtro che va in esecuzione prima di ogni richiesta
* all‘applicazione e marca il thread con le informazioni di contesto per il Log4J:
* nome host e utente (se presente in sessione)
*/
public class LoggingFilter implements Filter
{
FilterConfig fc;
public LoggingFilter()
{
}
public void init(FilterConfig p0) throws ServletException
{
this.fc = filterConfig;
}
/**
* Il metodo verifica se è presente l‘utente in sessione. Se è presente marca il thread
* corrente con il nome host e l‘utente altrimenti solo con il nome host.
* Per identificare il thread corrente si utilizza la classe org.apache.log4j.NDC di
* Log4J.
* Quando il thread termina l‘elaborazione
* l‘informazione NDC è rimossa per evitare spreco
* di risorse sul server.
* @param req
* @param res
* @param chain
* @throws java.io.IOException
* @throws javax.servlet.ServletException
*/
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException
{
HttpServletRequest request = (HttpServletRequest) req;
Utente utente = (Utente) request.getSession().getAttribute("utente");
if (utente != null)
{
NDC.push(request.getRemoteHost() + ":" + utente.getDes_nome_login());
}
else
{
NDC.push(request.getRemoteHost());
}
chain.doFilter(req, res);
NDC.pop();
NDC.remove();
}
public void destroy()
{
}
}
Configurando il filtro opportunamente nel web.xml dell‘applicazione si aggiungerà all‘intera applicazione la funzionalità richiesta senza toccare una riga di codice dell‘applicazione esistente.
I listener
Come il nome suggerisce i listener sono classi che rimangono in ascolto di particolari eventi; il listener va in esecuzione quando l‘evento per il quale è in ascolto si verifica. Nell‘ambito della Servlet API sono definite alcune interfacce che le classi listener devono implementare. Le interfacce determinano a quale evento il listener è in ascolto e su quali oggetti è possibile intervenire nel codice del listener.
Le interfacce definite nella Servlet API che un listener deve implementare sono le seguenti:
javax.servlet.ServletContextListener
Un oggetto di una classe che implementa questa interfaccia riceve notifiche sulla creazione e la distruzione del ServletContext di una applicazione tramite i metodi contextInitialized() e contextDestroyed(). I due metodi ricevono in input un oggetto della classe javax.ServletContextEvent che consente di accedere al ServletContext creato o in fase di distruzione. Un listener di questo tipo è molto utile per effettuare operazioni di inizializzazione di una applicazione web.
javax.servlet.ServletContextAttributeListener
Un oggetto di una classe che implementa questa interfaccia riceve notifiche sull‘aggiunta o sull‘eliminazione di attributi del ServletContext di una applicazione tramite i metodi attributeAdded(), attributeRemoved() e attributeReplaced(). I metodi ricevono in input un oggetto della classe javax.servlet.ServletContextAttributeEvent che consente di acquisire il nome ed il valore dell‘attributo aggiunto, rimosso o rimpiazzato nel ServletContext. Un listener di questo tipo può essere usato per tenere traccia degli oggetti che vengono memorizzati nel contesto dell‘applicazione.
javax.servlet.http.HttpSessionListener
Un oggetto di una classe che implementa questa interfaccia riceve notifiche sulla creazione o la distruzione di una sessione nell‘applicazione tramite i metodi sessionCreated() e sessionDestoyed().
I metodi ricevono in input un oggetto della classe javax.servlet.http.HttpSessionEvent mediante il quale è possibile acquisire un riferimento alla sessione oggetto dell‘evento di creazione o distruzione. Questo tipo di listener ha come suo utilizzo naturale quello di tenere traccia delle sessioni attive di una applicazione web e quindi del numero di utenti che vi accedono concorrentemente.
javax.servlet.http.HttpSessionActivationListener
Un oggetto di una classe che implementa questa interfaccia riceve notifiche sulla migrazione di una sessione in un‘altra JVM. Vi sono situazioni in cui una applicazione è distribuita tra più container e quindi tra diverse JVM. Mentre in una applicazione distribuita esiste un ServletContext per ogni JVM e un ServletConfig per ogni servlet di ogni container, ogni HttpSession è invece legata ad un singolo utente. Per cui in una applicazione distribuita una sessione deve migrare, con tutti i suoi attributi, da una JVM all‘altra, a differenza di quanto avviene per ServletContext e ServletConfig. Gli attributi memorizzati nella sessione devono quindi ricevere notifica di questo evento di migrazione della sessione, ovvero devono essere informati del fatto che la sessione sta per essere passivata nel container di origineo che è stata attivata nel container di destinazione. Per avere queste notifiche gli attributi di una sessione devono implementare l‘interfaccia in questione. Con il metodo sessionDidActivate() ricevono notifica del fatto che la sessione è stata appena attivata, mentre con il metodo sessionWillPassivate() ricevono informazione sul fatto che la sessione sta per essere passivata. I metodi ricevono in input un oggetto della classe javax.servlet.http.HttpSessionEvent mediante il quale è possibile acquisire un riferimento alla sessione oggetto dell‘evento
javax.servlet.http.HttpSessionAttributeListener
Un oggetto di una classe che implementa questa interfaccia riceve notifiche sull‘aggiunta o sull‘eliminazione di attributi da una sessione tramite i metodi attributeAdded(), attributeRemoved() e attributeReplaced(). I metodi ricevono in input un oggetto della classe javax.servlet.http.HttpSessionBindingEvent che consente di acquisire il nome ed il valore dell‘attributo aggiunto, rimosso o rimpiazzato nella HttpSession. Un listener di questo tipo può essere usato per tenere traccia degli oggetti che vengono memorizzati in una sessione utente.
javax.servlet.ServletRequestListener
Un oggetto di una classe che implementa questa interfaccia riceve notifiche sull‘inizializzazione e la distruzione di una richiesta mediante i metodi requestInitialized() e requestDestroyed(). I metodi ricevono in input un oggetto della classe javax.servlet.ServletRequestEvent che consente di acquisire un riferimento alla richiesta oggetto dell‘evento. Un listener di questo tipo può essere usato per avere informazioni su ogni richiesta che arriva ad una applicazione per effettuare ad esempio operazioni di logging.
javax.servlet.ServletRequestAttributeListener
Un oggetto di una classe che implementa questa interfaccia riceve notifiche sull‘aggiunta o sull‘eliminazione di attributi da una request tramite i metodi attributeAdded(), attributeRemoved() e attributeReplaced(). I metodi ricevono in input un oggetto della classe javax.servlet.ServletRequestAttributeEvent che consente di acquisire il nome ed il valore dell‘attributo aggiunto, rimosso o rimpiazzato nella request. Un listener di questo può essere usato per tenere traccia degli oggetti che vengono memorizzati in una request.
I listener vengono configurati nel web.xml dell‘applicazione aggiungendo una sezione come nell‘esempio seguente:
it.mokabyte.MioListener
Non è necessario specificare il tipo del listener perché il container lo desume ispezionando la classe registrata nel web.xml e vedendo quale interfaccia implementa. I listener vengono invocati nell‘ordine in cui sono definiti nel web.xml tranne i listener per gli eventi di shutdown (distruzione del contesto) che sono invocati in ordine inverso.
Esempi di applicazione dei listener potrebbero essere numerosi. Per avere un‘idea di alcune applicazioni di queste classi si possono analizzare gli esempi forniti in [8].
Conclusioni
Con questo articolo si conclude la serie dedicata alle web application Java EE, nata con lo scopo di fornire gli elementi di base necessari ad introdurre le tecnologie Java per lo sviluppo di applicazioni web. La realizzazione di applicazioni web professionali richiede ovviamente l‘approfondimento di queste conoscenze e delle tecnologie ad esse correlate.
Riferimenti
[1] Bryan Basham – Kathy Sierra – Bert Bates, “Head First. Servlet & Jsp”, O‘Reilly, 2004
[2] Autori Vari, “The J2EE 1.4 Tutorial “, Sun Microsystems, 2003
[3] Alfredo Larotonda, “Le applicazioni web e Java – II parte: le servlet”, MokaByte 111 Ottobre 2006
[4] Ceki Gà¼lcà¼, “Short Introduction to Log4J” Marzo 2002
http://logging.apache.org/log4j/docs/manual.html
[5] Sun Microsystems, Java 2 Platform Enterprise Edition, v.1.4 API Specification, 2003
http://java.sun.com/j2ee/1.4/docs/api/
[7] Jayson Falkner, “Two Servlet Filters Every Web Application Should Have”, OnJava.com, 19/11/2003
http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html?page=1
[8] Stephanie Fesler, “Servlet App Event Listeners”, OnJava.com, 12/04/2001
http://www.onjava.com/pub/a/onjava/2001/04/12/listeners.html?page=1