MokaByte 86 - Giugno 2004 
Sviluppare applicazioni J2EE con Jakarta Struts
VI parte: la gestione delle eccezioni
di
Alfredo Larotonda

Jakarta Struts è il framework più diffuso per lo sviluppo di applicazioni web J2EE. In questo articolo descriviamo gli strumenti messi a disposizione per la gestione delle eccezioni

Le eccezioni in Java
Le eccezioni sono il meccanismo che il linguaggio Java mette a disposizione per gestire eventuali situazioni anomale che possono verificarsi nell'esecuzione di una applicazione. Una eccezione è un oggetto di una determinata classe che viene creato quando una certa situazione di errore si è verificata in conseguenza dell'esecuzione di una applicazione. Le eccezioni fanno parte di una gerarchia di classi che ha come padre la classe java.lang.Throwable come rappresentato in figura.


Figura 1 - Gerarchia delle eccezioni


Da essa discendono due classi fondamentali che sono la java.lang.Exception e java.lang.Error. Le classi che discendono da Error rappresentano situazioni anomale dalle quali non è possibile in genere ripristinare l'applicazione, quali un OutOfMemory o uno StackOverflowError. Le classi che ereditano da Exception, e alle quali facciamo riferimento come eccezioni, invece rappresentano quelle situazioni anomale che lo sviluppatore può intercettare e gestire in base alla logica della propria applicazione.
Le eccezioni si suddividono ulteriormente in checked exceptions ed unchecked exceptions. La suddivisione fa riferimento all'obbligo da parte dello sviluppatore di dover gestire in qualche modo una eccezione o meno. Le unchecked exceptions sono sottoclassi di java.lang.RuntimeException che a sua volta è una sottoclasse di java.lang.Exception. Le eccezioni che discendono da RuntimeException possono essere gestite allo stesso modo di quelle checked ma non vi è l'obbligo di farlo, cioè il compilatore non impone che le eccezioni che estendono la RuntimeException vengano gestite.
La gestione delle eccezioni a livello di linguaggio viene fatta mediante l'uso del blocco try/catch e della clausola throws. Il codice che potenzialmente può generare eccezioni può essere messo all'interno di un blocco try/catch . Qualora si verifichi una eccezione il flusso elaborativo passa all'interno del blocco catch corrispondente all'eccezione generata, all'interno del quale viene messo il codice per la gestione dell'eccezione stessa, e prosegue con il codice seguente il blocco try/catch. Per le eccezioni di tipo checked questa gestione è obbligatoria, ovvero il compilatore non compilerà con successo la classe se il codice che può generare eccezione non è inserito all'interno di un blocco try/catch , a meno che il metodo stesso non dichiari che la gestione della eccezione è delegata al metodo chiamante. In tal caso si utilizza nella definizione del metodo la clausola throws. Mediante la clausola throws si dichiara quali eccezioni possono essere generate dal metodo in questione informando il chiamante sui propri obblighi in tale senso, tutto secondo la logica del design by contract . Nel caso in cui anche il metodo chiamante dichiari nella clausola throws l'eccezione in questione, l'elaborazione passerà all'ulteriore metodo chiamante e così via fino alla fine dello stack contenente la sequenza dei metodi.

 

Struts e la gestione delle eccezioni: approccio programmatico o dichiarativo
Dalla versione 1.1 di Struts sono state introdotte nel framework delle componenti che consentono di gestire le eccezioni in modo strutturato ed efficiente. I due approcci possibili nella gestione delle situazioni di eccezione, così come nella gestione di molte altre problematiche applicative ricorrenti nello sviluppo di applicazioni J2EE, sono quello programmatico e quello dichiarativo.
In generale l'approccio programmatico consiste nello scrivere all'interno della propria applicazione codice specifico per la gestione di una determinata problematica mentre l'approccio dichiarativo consiste nel configurare all'esterno dell'applicazione le modalità di gestione del problema stesso.
Nel caso delle eccezioni quindi, gestire una situazione di eccezione in modo programmatico significa inserire all'interno del proprio codice delle istruzioni specifiche per il trattamento dell'eccezione verificatasi.
L'approccio dichiarativo invece presuppone la configurazione all'esterno del codice del comportamento dell'applicazione a fronte del verificarsi di una eccezione.

Il primo approccio è quello tradizionale , utilizzabile in una qualsiasi applicazione Java e non presenta particolari differenze in una applicazione sviluppata con Struts. In questo caso lo sviluppatore deve inserire all'interno delle proprie classi il codice opportuno, ovvero i blocchi try/catch per la gestione delle eccezioni, né più né meno che in una qualsiasi applicazione Java.
Una Action contenente gestione programmatica delle eccezioni potrebbe avere un aspetto simile:

.
.
try{
  //codice che può generare eccezioni
}
catch(Exception e){
  ActionErrors errors = new ActionErrors();
  erorrs.add("eccezione", new ActionErorrs("eccezione"));
  saveErrors(request,errors);
  return mapping.findForward("eccezione");
}

In pratica si tratta di racchiudere il codice tra blocchi try/cath e nel catch corrispondente mettere il codice di gestione dell'eccezione che nell'esempio non è altro che la generazione di un errore da inviare al client, secondo la modalità già nota.
Questo approccio è possibile ed è sicuramente migliorabile personalizzando ad hoc il framework con classi generalizzate che accentrino gran parte della gestione necessaria, ma costringe comunque lo sviluppatore a scrivere codice ad hoc, spesso ridondante, per gestire queste situazioni. Inoltre in questo caso se si vuole modificare il comportamento dell'applicazione al verificarsi di una eccezione è necessario intervenire nel codice.
E' invece sicuramente più elegante ed efficiente sfruttare la possibilità offerta da Struts di gestire in modo dichiarativo le eccezioni. All'esterno del codice , quindi nel file XML di configurazione struts-config.xml, è possibile descrivere mediante opportune sezioni XML il comportamento che l'applicazione deve avere a fronte di una particolare eccezione. In questo modo, non solo si evita di scrivere codice specifico nell'applicazione per la gestione delle eccezioni, ma si ha la possibilità di modificare il comportamento dell'applicazione senza intervenire nel codice stesso.

 

Gli strumenti forniti da Struts per la gestione dichiarativa delle eccezioni
Il principio di base è quello di definire esternamente al codice il comportamento dell'applicazione e il conseguente flusso applicativo al verificarsi di una eccezione.
Questa configurazione può essere fatta a livello di singola Action o a livello globale per tutta l'applicazione.

Per configurare la gestione a livello di singola action si utilizza il tag <exception> all'interno della configurazione della Action stessa.
Nel tag <exception> si dichiara la classe dell'eccezione da gestire, la risorsa a cui verrà inoltrato il flusso elaborativo ed una chiave per acquisire dal resource bundle dell'applicazione un messaggio di errore come nell'esempio seguente:

<action path"/esempio"
  type="it.esempi.EsempioAction"
  input="input.jsp"
  scope="request"
  name="myForm" >
  <exception key="label.eccezione.esempio"
    type="it.esempi.MiaException"
    path="errorpage.jsp" />
</action>

In questo esempio si definisce che a seguito di una ipotetica MiaException il flusso applicativo sarà inoltrato alla pagina JSP errorpage.jsp acquisendo dal resource bundle il messaggio di errore corrispondente alla label label.eccezione.esempio.
In questo modo è possibile configurare tutti i comportamenti in corrispondenza delle possibili eccezioni generate dal metodo execute() di una Action.

E' anche possibile definire un comportamento globale utilizzando il tag <global-exception>.
In tal caso si presuppone la definizione di una o più classi handler per le eccezioni da gestire, ovvero delle classi che estendono org.apache.struts.action.ExceptionHandler ed il cui metodo execute() viene eseguito dal framework al verificarsi della corrispondente eccezione.
La configurazione di una eccezione a livello globale è fatta come nell'esempio seguente:

<global-exceptions>
  <exception key="label.eccezione"
    type="it.esempi.MiaException"
    handler="it.esempi.ExceptionHandler"/>
</global-exceptions>

Nella sezione <global-exception> si dichiara la classe dell'eccezione da gestire, la classe handler che conterrà la logica di gestione ed una chiave per acquisire dal resource bundle dell'applicazione un messaggio di errore.
Nell'esempio riportato si definisce che la classe it.esempi.ExceptionHandler , che estende org.apache.struts.action.ExceptionHandler, gestisce le eccezioni di tipo it.esempi.MiaException. Ciò significa che al verificarsi di una eccezione di questo tipo il framework manderà in esecuzione il metodo execute() della classe handler così dichiarata.
In questo metodo in genere è opportuno inserire del codice per effettuare il log dell'eccezione, memorizzando tutte le informazioni utili alla comprensione del problema che la ha generata, quali il messaggio associato all'eccezione, lo stack-trace etc. Il metodo restituisce in output un oggetto della classe ActionForward che consente di effettuare il forward alla risorsa desiderata.
E' ovvio che in entrambi i casi non sarà necessario inserire all'interno dell'applicazione alcun codice di gestione delle eccezioni perché il tutto è delegato alle classi handler.

 

Suggerimenti pratici
Nella pratica è sicuramente preferibile dichiarare delle global exceptions almeno per quelle eccezioni comuni a tutte le Action, lasciando alla dichiarazione della singola Action solo i casi particolari di eccezioni possibili solo in quel contesto. E' il caso ad esempio delle proprie eccezioni applicative definite per individuare particolari situazioni anomale riferite alla logica della propia applicazione.
E' comunque impensabile dover definire un numero elevato di classi handler per gestire tutte le possibili eccezioni. Se non sia necessaria una gestione estremamente particolareggiata è possibile dichiarare una classe handler per la java.lang.Exception che gestirà quindi in maniera centralizzata tutte le possibili eccezioni.

C'è infine da osservare che vi sono alcuni casi in cui non è possibile non inserire blocchi try/catch all'interno del codice. Basti pensare al caso di codice che esegue in modo programmatico una transazione su un database e che necessita di gestire le situazioni di eccezione per effettuare correttamente il commit o il rollback della transazione stessa.
In tal caso lo sviluppatore è obbligato ad inserire uno o più blocchi catch al cui interno eseguire l'operazione di rollback per annullare la transazione corrente.

 

Conclusioni
In questo articolo abbiamo affrontato l'argomento della gestione delle eccezioni, uno dei più ricorrenti e spesso trascurati nella pratica. Dopo aver introdotto brevemente il concetto di eccezione in Java abbiamo esaminato le possibilità offerte da Struts per affrontare questa problematica. La conclusione è senza dubbio che l'approccio dichiarativo è quello preferibile per la flessibilità che conferisce all'applicazione. Inoltre la configurazione è estremamente semplice e consente di risparmiare la scrittura di codice ripetitivo all'interno della propria applicazione.

 

Bibliografia
[1] Chuck Cavaness - "Programming Jakarta Struts", O'Reilly, 2003
[2] James Goodwill, Richard Hightower - Professional Jakarta Struts" - Wrox 2004


Alfredo Larotonda, laureato in Ingegneria Elettronica, lavora da diversi anni nel settore IT. Dal 1999 si occupa di Java ed in particolare dello sviluppo di applicazioni web J2EE. Dopo diverse esperienze di disegno e sviluppo ora si occupa in particolare di aspetti architetturali per progetti rivolti al mercato finanziario ed industriale. E' Web Component Developer certificato SUN per la piattaforma J2EE e Programmer per la piattaforma Java.


MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it