Chcete naštartovať projekt v Spring MVC? Jedna z možností je zvoliť klasický spôsob založený na XML, popísaný v samostatnom článku. Od Springu 3.2 sa môžeme úplne zbaviť XML a všetkú konfiguráciu spraviť v kóde. Ba čo je lepšie, prakticky sme úplne odtienení od servletového API!
Ako na to?
- Miesto
web.xml
využijeme vlastnýServletContainerInitializer
, v ktorom nastavíme springovský dispatcher servlet, mapovanie kontrolérov na URL adresy a triedu s konfiguráciou aplikačného kontextu. - Miesto XML s definíciou aplikačného kontextu použijeme triedu s anotáciou
@Configuration
.
Poďme na to.
Konfigurácia v Mavene
Na manažovanie závislostí (je ich totiž desať) použijeme Maven. Potrebujeme najmä:
spring-mvc
obsahujúci triedy frameworku Spring MVCservlet-api
vo verzii 3.0 a novšej, ktorý obsahuje základné API pre servlety
Popri tom môžeme rovno nakonfigurovať mavenovský plugin pre servletový kontajner Jetty: uľahčí sa nám spúšťanie projektu.
Využime nasledovný POM:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sk.upjs.ics.novotnyr</groupId>
<artifactId>springmvc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.0.4.v20130625</version>
</plugin>
</plugins>
</build>
</project>
Nastavenie inicializéra
Prvým krokom je vyhodenie web.xml
, miesto ktorého použijeme springovský webový inicializér. Ten stavia na mechanizme ServletContainerInitializer
-a zo špecifikácie servletov 3.0 a umožňuje v Java kóde nadefinovať všetky servlety, filtre a ostatné záležitosti, ktoré sú od nepamäti deklarované pomocou XML.
Stačí oddediť od AbstractAnnotationConfigDispatcherServletInitializer
a prekryť tri metódy:
package sk.upjs.ics.novotnyr.springmvc;
import org.springframework.web.servlet.support.*;
public class WebInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer
{
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] {
WebApplicationContext.class
};
}
@Override
protected String[] getServletMappings() {
return new String[] { "/*" };
}
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
}
Metóda getServletMappings()
V metóde getServletMappings()
definujeme predpis pre URL adresy, ktoré sa namapujú na springácky dispatcher servlet. V našom prípade všetky URL adresy (/*
) pôjdu cez tento servlet.
Ako to bolo v XML?
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Metóda getServletConfigClasses()
Táto metóda má vrátiť pole tried, ktoré v sebe nesú konfiguráciu aplikačného kontextu. V ukážke vraciame jednoprvkové pole s triedou sk.upjs.ics.novotnyr.springmvc.WebApplicationContext
, ktorú o chvíľočku popíšeme.
Ako to bolo v XML?
Názov XML sa odvodil buď konvenciou z názvu servletu v <servlet-name>
: ak sme mali servlet springmvc
, v adresári WEB-INF
sa hľadal springmvc-servlet.xml
. Namiesto toho sme mohli cestu uviesť explicitne:
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/*</url-pattern>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</init-param>
</servlet-mapping>
Ak sme využívali anotované konfiguračné triedy, mohli sme použiť aj:
<init-param>
<param-name>contextClass</param-name>
<param-value>sk.upjs.ics.novotnyr.springmvc.WebApplicationContext</param-value>
</init-param>
Metóda getRootConfigClasses()
Jednoduchá verzia: táto metóda nás netrápi, vždy vrátime null
.
Prečo je to tak? V tejto metóde môžeme vrátiť triedy anotované pomocou @Configuration
, ktoré obsahujú tzv. koreňový aplikačný kontext. Ako vieme, v Springu môžeme mať viacero aplikačných kontextov (teda “kontajnerov pre beany”), ktoré môžu medzi sebou dediť. V klasickej situácii môžeme mať koreňový kontext s definíciou beanov pre vrstvu biznis logiky a databázovej vrstvy. Popri tom si každý dispatcher servlet manažuje svoj vlastný aplikačný kontext, v ktorom sa nachádzajú kontroléry, a ostatné beany zodpovedné za prezentačnú vrstvu. Ak chce aplikačný kontext dispatcher servletu pristupovať k beanom koreňového kontextu, musí od neho oddediť a práve takýto vzťah dostaneme automaticky: stačí z metódy getServletConfigClasses()
vrátiť triedu s konfiguráciou servletového aplikačného kontextu a z metódy getRootConfigClasses()
vrátiť triedu s konfiguráciou aplikačného kontextu pre prostrednú vrstvu.
Ak nechceme mať kontexty oddelené (čo je typický príklad pre jednoduché projekty), koreňový kontext úplne odignorujeme a vrátime z tejto metódy null
.
Ako to bolo v XML?
Koreňový aplikačný kontext sa konfiguroval pomocou ContextLoaderListener
a.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
Nastavenie triedy s aplikačným kontextom
Trieda s definíciou aplikačného kontextu môže byť šokujúco jednoduchá:
package sk.upjs.ics.novotnyr.springmvc;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebMvc
@ComponentScan("sk.upjs.ics.novotnyr.springmvc")
public class WebApplicationContext {
/*
* všetky beany sa odhalia automaticky
*/
}
Pomocou @Configuration
označíme triedu za definíciu aplikačného kontextu.
Druhá anotácia @EnableMvc
zapne množstvo mágie pre Spring MVC. Za všetko spomeňme automatické zapnutie podpory pre anotované kontroléry, či mapovanie springáckych výnimiek na HTTP status kódy.
Tretia anotácia @ComponentScan
zase nastaví automatické vyhľadávanie (nielen) kontrolérov anotovaných pomocou @Controller
a ich registráciu v aplikačnom kontexte. V príklade sme nastavili automatickú registráciu z balíčka sk.upjs.ics.novotnyr.springmvc
.
Jednoduchý kontrolér
Následne môžeme vytvoriť jednoduchý kontrolér: stačí zobrať bežnú triedu s klasickou metódou a ošperkovať ju anotáciami:
package sk.upjs.ics.novotnyr.springmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class StudentController {
@RequestMapping("/student/1")
@ResponseBody
public String get() {
return "Janko Mrkvička";
}
}
Trieda je označená ako @Controller
, čím sa automaticky zaregistruje v aplikačnom kontexte (je v správnom balíčku a máme zapnutý @ComponentScan
). Jej metóda bude počúvať na URL adrese s príponou /student/1
(budeme tým napodobňovať filozofiu REST a vracať študenta s fiktívnym ID rovným 1).
Metóda vracia bežný reťazec a keďže máme na metóde reťazec @ResponseBody
, reťazec sa rovno a bez zmien ocitne v HTTP odpovedi.
Samozrejme, v starom spôsobe môžeme @ResponseBody
vynechať. V tom prípade sa bude hľadať view s logickým názvom Janko Mrkvička
, čo je samozrejme nezmysel. Konfiguráciu view resolverov však v tomto článku robiť nebudeme.
Testovanie
Vyskúšať celú funkčnosť môžeme naštartovaním servletového kontajnera Jetty pomocou Mavenu:
mvn jetty:run
Následne môžeme navštíviť:
http://localhost:8080/student/1
A uvidíme výsledok: text Janko Mrkvička v prehliadači.
Ukážkový projekt
Celý ukážkový projekt možno nájsť na GitHube.
vdaka za info. porovnanie so starym (XML based) pristupom je pre mna jedna z klucovych veci pre jednoduchsie porozumenie.
Viem si stále predstaviť situácie, keď je XML prístup možno lepší:
To sa dá doriešiť tiež implementáciou org.springframework.web.context.AbstractContextLoaderInitializer-a, len už to nie je také cool priamočiare.