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.
|