Nelle prime lezioni della guida abbiamo parlato dei componenti base che esistono nel contesto di Spring MVC. Uno di questi componenti è rappresentato dagli Handler Mapping: una serie di classi che hanno come scopo quello di associare un determinato URL ad un Controller in grado di esaudire la richiesta.
Gli Handler Mapping
Gli Handler Mapping definiti in una applicazione possono essere molteplici e possono rappresentare classi già disponibili in Spring MVC o possono essere componenti completamente personalizzati.
Questo argomento, nonostante la sua importanza, non è stato dettagliato all'inizio per il semplice motivo che, spesso, il comportamento di default previsto da Spring è più che sufficente per la maggior parte delle applicazioni. Andando avanti però con gli articoli, è ora giunta l'ora di approfondire anche questa tematica.
Nonostante il nome un po' ripetitivo, il RequestMappingHandlerMapping
è l'Handler Mapping utilizzato di default da Spring MVC in completa sintonia con il paradigma CoC (Convention over Configuration). Questo Handler sfrutta, come specificato nel proprio nome, le annotation @RequestMapping per creare un'associazione tra URL e metodo Java da invocare. Abbiamo già incontrato e approfondito l'annotation nella lezione dedicata ai Controller.
La dichiarazione XML che permette di utilizzare questo comportamento di default sarà:
<mvc:annotation-driven/>
Non esiste però solamente questo Handler...
- Il ControllerClassNameHandlerMapping permette di associare un Controller ad un URL sulla base del nome. Il suo comportamento di default, comunque personalizzabile, è quello di utilizzare il nome della classe in lower case e senza il suffisso "Controller" come URL. Per esempio il controller
ProvaController
sarà accessibile da/prova
eUserListController
da/userList
. - Il BeanNameUrlHandlerMapping utilizza il nome del bean Spring definito nel file XML (che ovviamente deve essere un Controller) come URL di destinazione della chiamata.
- Infine il SimpleUrlHandlerMapping permette di configurare una mappatura manuale all'interno del file XML di configurazione sfruttando anche i meta-caratteri come l'asterisco.
Ovviamente, come per tutti i componenti descritti fino a questo momento, anche gli Handler Mapping possono essere personalizzati completamente sulla base delle specifiche dell'applicazione: potremmo per esempio aver bisogno di intentificare il controller da invocare effettuando una query su database. Spring MVC ci viene in aiuto grazie all'interfaccia HandlerMapping che espone tre metodi da implementare nelle nostre classi concrete. Una volta definito il nostro HandlerMapping, non resta che annotarlo o inserirlo nella configurazione XML.
Per approfondire questo particolare argomento, può essere utile lettura del JavaDoc ufficiale.
Gli interceptor
Gli interceptor arricchiscono un componente molto caro agli sviluppatori J2EE tradizionali: i filtri.
Abbiamo già introdotto, seppur in maniera parziale, gli interceptor nel precedente articolo parlando di internazionializzazione e di cambio dinamico della lingua grazie appunto al LocaleChangeInterceptor, un particolare componente che rimaneva in ascolto su tutte le URL dell'applicazione e andava alla ricerca di un eventuale parametro (nel nostro caso era lang
) per modificare la lingua di navigazione dell'utente.
Gli interceptor servono appunto a questo, ad eseguire operazioni indipendenti dai controller in maniera completamente disaccoppiata evitando di ripetere codice nel momento in cui una funzionalità deve essere resa disponibile a più URL.
Ciascun interceptor può rimanere in ascolto su tre diversi eventi:
Evento | Descrizione |
---|---|
preHandle | prima dell'esecuzione del controller identificato dal Handler Mapping corrente (può anche eventualmente interrompere il flusso originale). |
postHandle | dopo l'esecuzione del controller ma prima che la vista corrente venga renderizzata. |
afterCompletion | al termine di tutto il flusso Request/Response, spesso utile per rilasciare alcune risorse altrimenti occupate. |
Per implementare il proprio Interceptor, Spring espone sia un'interfaccia (HandlerInterceptor
) sia una classe di utilità che implementa, in maniera trasparente i 3 metodi facilitandoci il lavoro nel caso volessimo riscriverne solamente uno (HandlerInterceptorAdapter
).
Esempio di Interceptor
Una volta definiti gli aspetti teorici concentriamoci nella definizione di un Interceptor custom, che si occupa di loggare (usando un semplice ma pericoloso System.out.println
– da evitare in ambienti reali) tutto quello che avviene durante una semplice chiamata HTTP.
Creiamo quindi la nostra classe che implementa HandlerInterceptor, perchè in questo caso ci servirà implementare tutti e tre i metodi presenti:
public class LogHandlerInterceptor implements HandlerInterceptor {
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
System.out.println("=== AFTER COMPLETION ===");
System.out.println("Response ContentType: " + response.getContentType());
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("=== POST HANDLE ===");
System.out.println("View name: " + modelAndView.getViewName());
System.out.println("Model: " + modelAndView.getModel());
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("=== PRE HANDLE ===");
System.out.println("Request URI: " + request.getRequestURI());
System.out.println("Request Method: " + request.getMethod());
System.out.println("Request Query String: " + request.getQueryString());
return true;
}
}
La classe è piuttosto semplice, nel caso del preHandler
stampiamo alcuni parametri contenuti nella Request, nella postHandle
il nome della vista che verrà ritornata all'utente e il modello di dati richiesto e infine nella afterCompletion
alcuni parametri sulla Response
.
Una volta definita la classe, iniettarla nel modello di Spring è facilissimo:
<mvc:interceptors>
<bean class="com.albertobottarini.springmvc.utils.LogHandlerInterceptor" />
</mvc:interceptors>
Ovviamente all'interno del tag interceptors possiamo inserire tutta una serie di HandlerInterceptor
.
Non prendiamo invece sottogamba l'ordine di inserimento dato che un interceptor può eventualmente bloccare la catena di esecuzione.