Dopo aver introdotto i concetti di base di internet e delle tecnologie web, iniziamo a vedere quali sono i componenti che la J2EE ci mette a disposizione per la realizzazione di applicazioni web. Parleremo delle servlet, del servlet context e dei web container. Realizzeremo poi una prima, semplice servlet, in vista degli argomenti più avanzati che saranno trattati negli articoli successivi della serie.
Introduzione
Nel precedente articolo abbiamo illustrato alcuni concetti di base su internet ed in particolare abbiamo introdotto l‘argomento delle applicazioni web. Con questo secondo articolo iniziamo a vedere cosa ci mette a disposizione la J2EE per costruire web application. Il supporto fornito dalle tecnologie Java in questo ambito è davvero vasto e comprende numerosissime specifiche e librerie che rispondono a tutte le esigenze che quotidianamente un progettista o uno sviluppatore di applicazioni web si trova a dover soddisfare. Non è un azzardo affermare che oggi la J2EE sia la piattaforma di riferimento per quel che riguarda le applicazioni web enterprise. Uno degli elementi che costituisce le fondamenta di tutta l‘architettura è la Servlet API di vedremo in questo articolo gli elementi di base. Occorre precisare che non è intenzione affrontare in questa sede tutti i dettagli della specifica ma piuttosto fornire una introduzione teorica necessaria per un successivo approfondimento.
Le servlet e il Web Container
Nel precedente articolo ci eravamo lasciati parlando di programmi CGI, ovvero quelle applicazioni che consentono ad un server web di generare risposte dinamiche a fronte di una richiesta.
L‘elemento della J2EE che assolve a questo compito è la Servlet. Inutile dire , parlando di Java, che una servlet non è altro che una classe, anche se un po‘ “speciale”, che consente di gestire il paradigma richiesta/risposta tipico di una web application.
Una servlet è una classe “speciale” non solo perché come vedremo ha una struttura ben definita ma anche perché agisce sotto il controllo di un‘altra applicazione Java chiamata Container. Il ciclo di vita di una oggetto di classe Servlet è gestito completamente dal Container che controlla tutte le fasi della servlet stessa: crea gli oggetti che contengono la richiesta del client e la risposta da inviare al client, invoca i metodi della servlet e ne gestisce creazione e distruzione.
È il container quindi che crea una servlet, gestisce le richieste che arrivano dai client, costruisce un oggetto che rappresenta la richiesta e lo passa in input ad un opportuno metodo di una servlet, gestisce la risposta da fornire ad un client ed elimina la servlet dalla memoria.
Il Web Container è un componente fondamentale nell‘architettura J2EE; è l‘ambiente di esecuzione delle componenti (Servlet e JSP) del livello web (Web Tier) di un‘applicazione enterprise e gestisce numerose funzionalità di infrastruttura svincolando in questo modo lo sviluppatore dall‘occuparsi di task complessi quali la gestione della comunicazione con i client, il ciclo di vita delle servlet, il supporto al multithreading, la sicurezza. In questo modo lo sviluppatore si può concentrare sullo sviluppo della logica di business della propria applicazione perché è il container che si occupa dei task di infrastruttura appena elencati. Un esempio di web container è Tomcat, uno dei prodotti open source più diffusi e utilizzati. Il Web Container non è il solo container previsto nell‘architettura J2EE; un application server J2EE prevede infatti anche la presenza di un EJB Container nel quale risiedono i componenti che implementano la logica di business, gli Enterprise Java Bean. La descrizione del EJB Container non è oggetto di questo articolo che tratta del solo web tier della piattaforma J2EE.
Il Web Container gestisce le richieste client eseguendo una serie di operazioni; una volta che l‘utente clicca su di un link corrispondente ad una servlet il container intercetta la richiesta e crea due oggetti che implementano le interfacce standard javax.servlet.Request e javax.servlet.Response. Una volta creati i due oggetti individua la servlet invocata dal client e se non esiste in memoria la istanzia e la inizializza, altrimenti se è già istanziata e inizializzata crea un nuovo thread della servlet stessa e le passa i due oggetti creati come parametri del metodo service(). Il metodo della servlet genera la risposta e la memorizza nell‘oggetto Response precedentemente creato dal container. A questo punto il container invia la risposta al client , il thread termina e i due oggetti creati vengono distrutti.
Tutto quanto avviene senza che lo sviluppatore debba preoccuparsi di ciò, il che fa capire l‘importanza ed il ruolo del web container. In Figura 1 è raffigurato uno schema di quanto descritto.
Figura 1 – Il Web Container
Il ciclo di vita di una Servlet
Abbiamo accennato al fatto che una servlet è una classe Java che consente di gestire il meccanismo richiesta/risposta e che la vita di questo tipo di oggetto è gestita interamente dal web container. Il compito di una servlet è quindi quello di servire le richieste dei client. Quello che rende una classe Java una Servlet è il fatto di implementare una interfaccia standard, la javax.servlet.Servlet, che definisce i metodi del ciclo di vita. La javax.servlet.GenericServlet è una classe astratta che implementa in modo standard molti dei metodi definiti nell‘interfaccia javax.servlet.Servlet. Infine vi è la javax.servlet.http.HttpServlet, un‘altra classe astratta che estende javax.servlet.GenericServlet ed implementa i metodi del ciclo di vita specifici per il protocollo HTTP. La Servlet API è infatti scritta in modo da prevedere una struttura indipendente dal tipo di protocollo utilizzato; noi faremo riferimento alle servlet che rispondono a richieste HTTP anche perché nella pratica sono quelle utilizzate, anche se nulla vieta di pensare ad una servlet che non sia di tipo http.
Scrivere una Java Servlet si traduce quindi nello sviluppare una classe che estende la javax.servlet.http.HttpServlet.
Il diagramma UML delle classi è raffigurato in Figura 2.
Figura 2 – Interfacce e classi base della Servlet API
I metodi che caratterizzano il ciclo di vita di una servlet vengono detti metodi di callback in quanto è il container che li invoca e non altri oggetti della nostra applicazione.
I metodi principali del ciclo di vita di una servlet sono:
- init(javax.servlet.ServletConfig)
È il metodo che il container esegue dopo aver creato l‘istanza della servlet alla prima invocazione della stessa e prima che questa possa servire una qualsiasi richiesta client. Può essere utile per compiere operazioni di inizializzazione ed in genere ne viene fatto l‘override nella propria classe.
- service(javax.servlet.ServletRequest,javax.servlet.ServletResponse)
È il metodo che serve le richieste provenienti dai client. È chiamato dal container ad ogni invocazione di una servlet dopo aver creato il thread che serve la richiesta. Il metodo service() analizza la richiesta e delega l‘elaborazione al doGet() o al doPost(), i metodi che lo sviluppatore andrà ad implementare nella propria classe per l‘elaborazione della richiesta http.
- destroy()
È il metodo invocato dal container quando la servlet deve essere rimossa dalla memoria.
Il ciclo di vita di una servlet è raffigurato in Figura 3.
Figura 3 – Il ciclo di vita di una servlet
Dallo stato in cui la servlet non esiste si passa nello stato inizializzato o perché il container in fase di inzializzazione della web application è configurato per inizializzare tutte le servlet o perché è arrivata da un client la prima richiesta alla servlet stessa. In questa transizione il container invoca il costruttore della servlet; ma un oggetto per essere una servlet oltre ad essere istanziato deve essere inizializzato opportunamente , e a questo pensa il metodo init(ServletConfig) che fornisce in input alla servlet un oggetto di un tipo che implementa l‘interfaccia javax.servlet.ServletConfig contenente tutte le informazioni di configurazione della servlet stessa. Ogni servlet ha il proprio oggetto che implementa l‘interfaccia javax.servlet.ServletConfig. Una volta che la servlet è inzializzata, è pronta a servire le richieste dei client. Ad ogni richiesta il container invoca il metodo service() che in base al tipo della richiesta HTTP delega l‘elaborazione al metodo doGet(HttpServletRequest,HttpServletresponse) o doPost(HttpServletRequest,HttpServletresponse) passando in input i due oggetti che wrappano la richiesta e la risposta. La servlet rimane in questo stato di servizio fino a che il container non ha necessità di rimuoverla. In questo caso viene chiamato il metodo destroy() e la servlet cessa di esistere.
Le servlet e il multithreading
Come già detto il compito di una servlet è quello di servire le richieste provenienti dai client. È evidente che i client di una applicazione web possono essere molti e richiedere servizi all‘applicazione tutti contemporaneamente. La gestione della concorrenza di richieste ad una stessa servlet è gestita dal container secondo una logica di multithreading. È bene sapere che esiste una ed una sola istanza di ogni servlet nel container, ma ad ogni richiesta viene creato, o reperito da un pool dedicato, un thread per servire la richiesta. Le servlet quindi sono basate su un meccanismo single-istance ma multithread. Il compito di gestire i thread è tutto a carico del container, ma chi sviluppa una servlet deve aver ben presente che ha sempre a che fare con una sola istanza e prendere di conseguenza accorgimenti opportuni nello sviluppo. Ad esempio è buona norma non definire variabili di istanza in una servlet che porterebbero a comportamenti imprevedibili.
Il concetto fondamentale da tenere in mente è: una richiesta, un thread.
In realtà esiste nella specifica l‘interfaccia javax.servlet.SingleThreadModel; è una interfaccia senza metodi che se implementata da una servlet garantisce che due thread concorrenti non eseguiranno contemporaneamente il metodo service(). Anche se questa potrebbe sembrare la migliore soluzione ai problemi di concorrenza, l‘uso di questa interfaccia è invece da evitare poiché riduce notevolmente le prestazioni di una applicazione che non potrà usufruire più della gestione multithread che è una caratteristica essenziale delle servlet.
La prima servlet: Hello World!
Dopo aver esaminato cosa sia una servlet ed aver visto alcune delle sue caratteristiche fondamentali possiamo provare a vedere come si presenta una servlet a livello di codice Java. Non poteva mancare quindi la classica HelloWorldServlet, che quando invocata restituisce nella risposta il codice HTML di una pagina con il classico saluto “Hello World”.
Si possono notare le import dei package che costituiscono le librerie della Servlet API, il metodo init() e i due metodi doGet() e doPost(). La servlet risponde in questo caso allo stesso modo ad una richiesta GET o POST e produce nella risposta un flusso HTML corrispondente ad una pagina recante la scritta “HELLO WORLD”.
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloWorldServlet extends HttpServlet {
private static final String CONTENT_TYPE = "text/html;
charset=windows-1252";
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("");
out.println("HelloWorldServlet ");
out.println("");
out.println("HELLO WORLD
");
out.println("");
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String var0 = "";
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("");
out.println("HelloWorldServlet ");
out.println("");
out.println("HELLO WORLD
");
out.println("");
out.close();
}
}
Quello che resta da capire è come invocare nella propria applicazione questa banale servlet di saluto. Come visto nel precedente articolo ad ogni risorsa web corrisponde un URL che la individua. Supponendo che il nome della applicazione che contiene la nostra servlet sia miaapp l‘URL corrispondente alla nostra servlet è il seguente:
http://hostname:porta/miaapp/helloworld
Conosciamo già il significato di hostname e porta ai quali segue il nome che individua l‘applicazione alla quale inviamo la nostra richiesta. Per capire come fa il container ad individuare la singola servlet ci resta da interpretare l‘ultima parte del URL , ovvero helloworld. Per associare una classe servlet ad un nome logico si effettua il cosiddetto Servlet Mapping. La definizione del mapping delle servlet di una applicazione è effettuata nel file web.xml il deployment descriptor che ogni applicazione web J2EE deve avere in base allo standard. Di seguito è riportato il web.xml di una applicazione nella quale viene effettuato il mapping della nostra servlet. In grassetto sono evidenziate le sezioni di interesse. Tra i tag
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4" >
Esempio di web.xml file per una Web Application
HelloWorldServlet
servlet.HelloWorldServlet
HelloWorldServlet
/helloworld
35
html
text/html
txt
text/plain
Nei prossimi articoli vedremo come si effettua il deploy di una servlet in un web container e applicheremo quindi le nozioni teoriche appena illustrate.
Il Servlet Context
Abbiamo quindi capito come le servlet siano la spina dorsale di ogni applicazione web J2EE. Sono quei componenti dell‘architettura ai quali è delegato il compito di servire le richieste provenienti dai client e di rispondere opportunamente. L‘ambiente di esecuzione delle servlet è il web container, elemento fondamentale in quanto fornisce numerose funzionalità di infrastruttura vitali per il corretto funzionamento della nostra web application. Abbiamo visto inoltre come il container associ a ciascuna servlet un oggetto che ne rappresenta le impostazioni di configurazione. Questo oggetto implementa l‘interfaccia javax.servlet.ServletConfig. Oltre a ciò una servlet deve anche avere informazioni sul container stesso, sul proprio ambiente di esecuzione. A tale scopo il web container costruisce per ciascuna applicazione web deployata al suo interno un altro oggetto il ServletContext, o meglio un oggetto di una classe che implementa l‘interfaccia javax.servlet.ServletContext. Il ServletContext è un oggetto visibile da tutte le servlet dell‘applicazione (e da tutte le JSP come vedremo) e contiene informazioni relative al container stesso utili alla servlet per la sua elaborazione. È bene ricordarsi che esiste un ServletContext per ogni applicazione ed un ServletConfig per ogni servlet di una applicazione.
Il ruolo delle servlet
I dettagli della Servlet API sono davvero molti ed è impossibile esaurirli in un solo articolo. Nei paragrafi precedenti abbiamo introdotto gli elementi di base più che altro per dare un‘idea di cosa siano le servlet e di quale sia il loro ruolo nella piattaforma J2EE. Come si è detto più volte il compito è quello di servire le richieste dei client. All‘interno di un metodo di una servlet in linea teorica è possibile scrivere qualsiasi cosa trattandosi di metodi di classi Java assolutamente non differenti dai metodi di una qualsiasi altra classe. È bene però puntualizzare sin da ora che non è compito delle servlet quello di implementare la logica di business di una applicazione web nà© quello di costruire l‘interfaccia da presentare all‘utente. Nell‘esempio visto precedentemente all‘interno dei metodi della servlet veniva scritto il codice HTML da inviare in risposta al client. Questo è chiaramente solo il primo esempio ed è didatticamente utile sapere che questo si può fare soprattutto per capire il funzionamento di un altro elemento fondamentale della J2EE che vedremo nel prossimo articolo, le pagine JSP. Nella pratica reale però non si scriverà mai e poi mai il codice HTML all‘interno di un metodo di una servlet, in quanto il compito della presentazione dei dati all‘utente non deve essere assolto dalle servlet. Il ruolo di una servlet è quello di controller; le servlet dirigono il flusso applicativo a fronte degli input forniti dall‘utente e delegano alle opportune classi di business logic l‘elaborazione applicativa di una richiesta, riacquisendo il controllo una volta che questa elaborazione è terminata per preparare la risposta al client.
Nel ben noto pattern architetturale MVC (Model View Controller) che prevede la separazione logica dei livelli di una applicazione, le servlet implementano il ruolo di Controller.
Conclusioni
In questo articolo è stato introdotto l‘argomento delle servlet, la spina dorsale delle applicazioni web in Java. L‘intento era di fornire un‘introduzione di natura più concettuale che pratica per far capire soprattutto a chi per la prima volta si avvicina allo sviluppo di applicazioni web in Java quali siano gli strumenti che la J2EE mette a disposizione. Nel prossimo articolo introdurremmo l‘altro mattone fondamentale del web tier della architettura J2EE: le Java Server Pages.
Riferimenti
[1] Bryan Basham – Kathy Sierra – Bert Bates, “Head First: Servlet & Jsp”, O‘Reilly, 2004
[2] AAVV, “The J2EE 1.4 Tutorial “, Sun Microsystems, 2003