Uno degli argomenti troppo spesso sottovalutati durante lo sviluppo di una applicazione Web riguarda la gestione degli errori. Una applicazione Web offre molti "punti di contatto" con gli utenti; per questo motivo è necessario dotarsi di una buona architettura per evitare di incappare in problemi che possono essere dannosi non solo per l'utente ma anche per la stabilità della applicazione.
SpringMVC non tralascia questi aspetti e offre agli sviluppatori una serie di possibilità per gestire al meglio le eccezioni che possono essere lanciate durante la vita di un programma. All'interno del framework esistono due diverse macro strategie per la gestione delle eccezioni:
- utilizzare un bean di tipo
ExceptionResolver
configurato genericamente nel contesto dell'applicazione; - utilizzare l'annotation
@ExceptionHandler
per gestire in maniera più puntuale l'errore.
Non esiste una strategia migliore dell'altra: quelle elencate sono due facce della stessa medaglia che devono essere scelte in base alla tipologia di applicazione che stiamo sviluppando; per essere più precisi, bisogna però ricordare che l'utilizzo l'annotation @ExceptionHandler
deriva comunque da un ExceptionResolver
che, a meno di modifiche, è però attivo di default, offrendo di fatto due diverse scelte architetturali.
Gli ExceptionResolver
Gli ExceptionResolver
non sono nient'altro che delle classi che, configurate opportunamente all'interno del contesto di Spring, si occupano appunto di intercettare e gestire un'eccezione evitando che essa venga mostrata direttamente all'utente.
Il SimpleMappingExceptionResolver
permette di creare una mappatura tra tipologia di eccezione e nome della vista da restituire all'utente. La mappatura può essere fatta direttamente tramite configurazione XML:
<bean class="org.springframework.Web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="com.albertobottarini.Eccezione1">nomevista1</prop>
<prop key="com.albertobottarini.Eccezione2">nomevista2</prop>
</props>
</property>
<property name="defaultErrorView" value="defaultvista" />
</bean>
All'interno della proprietà exceptionMappings
è possibile definire una mappa chiave/valore che presenta come chiave il nome dell'eccezione e come valore il nome della vista (che verrà risolta con il viewResolver definito). È inoltre possibile definire una vista di errore di default grazie alla proprietà defaultErrorView
.Tutti gli ExceptionResolver implementano l'interfaccia HandlerExceptionResolver
, questo permette quindi di definire senza particolari criticità un proprio resolver per offrire all'utente un'esperienza diversa e più completa.
@ExceptionHandler
La seconda possibilità per gestire le eccezioni avviene tramite alcune annotations e in particolare @ExceptionHandler
e @ResponseStatus
. Anche questa possibilità, come detto prima, si basa su alcuni ExceptionResolver che sono abilitati di default, coerentemente con il paradigma Convention Over Configuration. L'annotation @ExceptionHandler
permette di definire un metodo all'interno dei nostri controller che verrà invocato automaticamente quando verrà lanciata, all'interno del controller specifico, una particolare eccezione (identificata come parametro dell'annotation).
@Controller
public class TestExceptionController {
@ExceptionHandler(MyException.class)
public String gestoreEccezioni(MyException ex) { return "errorView"; }
@RequestMapping(value = "/")
public String handler() {
throws new MyException("An error occured!"); return null;
}
}
In questo caso abbiamo un banalissimo controller dotato di un unico entry point (all'url "/"). Invocando la URL il controller lancerà una eccezione che però verrà gestita, grazie ad una apposita vista, dal metodo annotato con @ExceptionHandler
; attenzione però che le uniche eccezioni gestite saranno quelle istanziate dalla classe MyException
all'interno di questo controller.
L'annotation @ExceptionHandler
può essere utilizzata in combinazione con @ResponseStatus
per forzare un particolare codice di ritorno (ed un eventuale messaggio di errore):
@ExceptionHandler(MyException.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Messaggio di errore personalizzato!")
public String gestoreEccezioni(MyException ex) {
return "errorView";
}
Ultimo suggerimento: nel caso volessimo usare questa seconda strategia basata su annotation in maniera più globale rispetto alla totalità dell'applicazione abbiamo due possibilità:
- usare un controller astratto esteso da tutti gli altri che contiene gli
@ExceptionHandler
; - creare un controller vuoto annotato con
@ControllerAdvice
che permette di creare configurazioni comuni a tutti i futuri controller.
L'importanza della gestione delle eccezioni
Nonostante possa sembrare un argomento di facile comprensione e banale, la gestione delle eccezioni è un aspetto importante che non deve essere preso sottogamba. Una buona organizzazione delle classi e una buona progettazione delle eccezioni, magari utilizzandone di personalizzate, possono facilitare notevolmente lo sviluppo rendendo inoltre più completa la navigazione da parte dagli utenti.
Non dimenticate che incappare in errori di questo tipo non è per nulla difficile, basterà anche un solo parametro errato in una query string o un link ad un'area riservata; molto del "lavoro sporco" viene svolto senza dubbio da Spring MVC, ma alcune responsabilità rimangono, giustamente, in mano agli sviluppatori... quindi prestiamo attenzione!