In questa seconda parte della serie di articoli dedicati a Spring MVC vediamo come “convertire” la semplice applicazione servlet per il login utente presentata nell’articolo Primi Passi con Spring MVC (parte 1), in una applicazione Spring MVC.
Innanzitutto accenniamo brevemente al significato dell’acronimo MVC, che è l’abbreviazione per Model-View-Controller. Con tale termine si indica un pattern architetturale, principalmente utilizzato nella programmazione orientata agli oggetti, per indicare una separazione netta e distinta tra: il modello dei dati, la vista che li visualizza, ed il controllore che li manipola. Ovviamente tali concetti li ritroveremo in Spring MVC.
Per l’utilizzo di Spring MVC dobbiamo innanzitutto inserire nel pom.xml
la relativa dipendenza che naturalmente si aggiunge a javaee-web-api
che implementa le Servlet API:
1 2 3 4 5 6 7 8 9 10 11 |
<dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.2.3.RELEASE</version> </dependency> |
Relativamente alle viste, invece, le pagine jsp del progetto non subiscono alcuna modifica.
DispatcherServlet
Molti framework java orientati al web si fondano sulle Servlet API ed implementano una servlet dedicata come unico punto di accesso alle funzionalità del framework. In JSF, ad esempio, è presente la FacesServlet,
mentre in Struts abbiamo la ActionServlet.
Spring MVC utilizza invece la DispatcherServlet,
che implementa molti degli algoritmi comuni mentre demanda operazioni specifiche a componenti delegate configurabili. La servlet può essere configurata utilizzando JavaConfig oppure, più tradizionalmente, dichiarandola nel descrittore web.xml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>Webapp</display-name> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/app-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> |
La servlet è configurata utilizzando un contesto di Spring orientato al web e che è definito dalla interfaccia WebApplicationContext
che estende ApplicationContext
. Il parametro contextConfigLocation
indica alla Servlet dove si trova l’XML per la configurazione del contesto di Spring e che, nel nostro caso, viene interpretato mediante la classe XmlWebApplicationContext
. Il file app-config.xml
che definisce il contesto di spring è il seguente:
1 2 3 4 |
<beans xmlns="http://www.springframework.org/schema/beans" ... > <context:component-scan base-package="it.javaboss" /> <mvc:annotation-driven/> </beans> |
<context:component-scan>
: consente di eliminare la necessità di configurare tutti i bean nell’XML indicando a Spring (core) i package dal quale iniziare lo scan alla ricerca di classi annotate con@Component
,@Repository
,@Service
e@Controller
. Utilizzando questo tag non è necessario inserire nell’XML anche il tag<context:annotation-config>
che invece indica a Spring di riconoscere le annotazioni@Autowire
,@Required
e@Qualifier
per l’iniezione delle componenti.<mvc:annotation-driven/>
: in una applicazione MVC indica a Spring di riconoscere i relativi componenti, cosa che già avverrebbe grazie alla presenza del tagcontext:component-scan>
. Inoltre forza la registrazione nel contesto di Spring di alcuni bean (di default) che altrimenti dovrebbero essere inseriti esplicitamente nell’XML, e l’attivazione di alcune configurazioni. Per un approfondimento rimando a questo ottimo post spring-mvc-component-scan-annotation-config-annotation-driven.
Controller
Il ruolo svolto dalla servlet nel progetto originale è ora svolto da un oggetto controller. Diversamente dal caso della servlet, in cui è necessario estendere la classe javax.servlet.http.HttpServlet
, in Spring MVC il controller non deve estendere o implementare alcuna interfaccia, ma la sola operazione richiesta è di annotare il bean con l’annotazione @Controller
.
1 2 3 4 |
@Controller public class LoginController { ... } |
Per effetto della direttiva component-scan
tutte le classi annotate in tale modo vengono riconosciute come controller per le richieste HTTP provenienti dal front-end. L’associazione del bean alla url gestita ed al metodo HTTP avviene invece per mezzo di un’altra annotation @RequestMapping
che può essere associata sia alla classe che ai metodi della classe per una più fine configurazione. Per replicare quanto fatto nella servlet originale abbiamo bisogno di un metodo che mostri la pagina di login e di uno che gestisca la sottomissione della form. Il controller diviene quindi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Controller public class LoginController { @Autowired LoginService service; @RequestMapping(value = "/login", method = RequestMethod.GET) public String showLoginPage() { return "login"; } @RequestMapping(value = "/login", method = RequestMethod.POST) public String handleUserLogin( ModelMap model, @RequestParam String name, @RequestParam String password) { if ( service.validateUser( name, password) ) { model.put("name", name); model.put("password", password); return "welcome"; } else { model.put("errorMessage", "Invalid Credentials!!"); return "login"; } } } |
Come è evidente dal codice il metodo showLoginPage()
è invocato dalla DispatcherServlet
quando dal browser arriva una richiesta GET
sull’url /login
, mentre il metodo handleUserLogin()
è invocato quando arriva una richiesta POST
sullo stesso url.
Model
Per il recupero dei parametri associati alla request il controller non ha bisogno di accedere all’oggetto HttpServletRequest,
come nel caso delle servlet, ma Spring implementa un meccanismo dichiarativo in cui, attraverso annotation, indica quali parametri devono essere passati al controller. Il metodo handleUserLogin()
, ad esempi, dichiara i parametri name
e password
annotandoli con @RequestParam
. Questo indica alla DispatcherServlet
che nella request HTTP saranno presenti due parametri, rispettivamente name
e password,
che dovrà recuperare e passare al metodo.
In modo analogo esistono altre annotation con cui possono essere annotati i parametri di un metodo del controller al fine di accedere a diversi elementi della request, come ad esempio @RequestHeader, @CookieValue,@RequestBody, etc., ma non saranno oggetto del presente articolo.
Un altro parametro che svolge un ruolo significativo ne metodo handleUserLogin()
è model
di tipo org.springframework.ui.ModelMap
. Si tratta dell’oggetto che sarà passato alla vista per il rendering dei valori passati in response, ovvero quelli che nel caso della servlet erano inseriti nell’oggetto HttpServletResponse
.
ViewResolver
Se proviamo ad eseguire l’applicazione così com’é ed inseriamo nel browser l’url http://localhost:8080/login otterremo il seguente errore:
HTTP Status 500 - Circular view path [login]: would dispatch back to the current handler URL [/login] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
Questo accade perchè i metodi del controller restituiscono i nomi delle viste su cui redirigere il browser e nel caso di showLoginPage()
il nome restituito è login
che è esattamente l’url definito nell’annotazione @RequestMapping
del metodo stesso. Questo provoca appunto un Circular view path
.
Quello che manca è di istruire il DispatcherServlet
in modo che il nome della vista login
sia associato la pagina login.jsp
definita nel percorso /WEB-INF/views
. Questa mapping è ottenuto introducendo il componente ViewResolver.
Spring MCV mette a disposizione diversi ViewResolver
in funzione di come si realizzi il mapping richiesto. Nel nostro caso abbiamo bisogno di configurare un InternalResourceViewResolver
in quanto le jsp sono inserite all’interno della cartella WEB-INF
e quindi sono, per definizione, internal resource view e quindi accessibili esclusivamente tramite servlet (o controller).
Per configurare il ViewResolver
è sufficiente definire il relativo bean nel file app-config.xml
come segue:
1 2 3 4 5 6 7 8 |
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix"> <value>/WEB-INF/views/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean> |
L’effetto è quello di modificare il nome delle viste restituite dai controller inserendo il prefisso WEB-INF/views/
ed un suffisso .jsp
. Conseguentemente le viste restituite dai metodi del nostro controller saranno mappate in:
login |
WEB-INF/views/login.jsp |
welcome |
WEB-INF/views/welcome.jsp |
Il Processo
Ricapitoliamo il processo che è utilizzato da Spring MVC per soddisfare una richiesta HTTP proveniente all’applicazione. Per farlo ci avvaliamo dell’immagine seguente, rubata dal sito onlinetutorialspoint.
Consideriamo il caso, più completo, della sottomissione della maschera di login:
- Il browser esegue una request di tipo
POST
all’url/login
che è intercettata dal Front Controller ovvero dallaDispatcherServlet
. - La servlet decide a quale controller delegare la gestione della richiesta in base all’header della request ed, ovviamente, alla configurazione del contesto MVC.
- Il metodo
handleUserLogin()
della classeLoginController
prende in carico la richiesta e la elabora. - Terminata l’elaborazione il controller popola il model, in funzione dell’esito.
- Il controller restituisce il model al Front Controller.
- La servlet delega la costruzione della vista ad un view template identificato in base al utilizza il
ViewResolver.
- La vista è generata in base al model ed alla jsp ed è restituita la Front Controller.
- La
DispatcherServlet
invia la pagina generata al browser.
Codice Sorgente
Il codice sorgente completo e funzionante dell’applicazione è scaricabile qui login-sprmvc.