Aha, aplikácia Spring MVC bez XML!

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 MVC
  • servlet-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 ContextLoaderListenera.

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

2 thoughts on “Aha, aplikácia Spring MVC bez XML!

  1. vdaka za info. porovnanie so starym (XML based) pristupom je pre mna jedna z klucovych veci pre jednoduchsie porozumenie.

  2. Viem si stále predstaviť situácie, keď je XML prístup možno lepší:

    • viac Dispatcher Servletov
    • vlastné filtre a ďalšie servlety: za všetky napríklad wickeťácky.

    To sa dá doriešiť tiež implementáciou org.springframework.web.context.AbstractContextLoaderInitializer-a, len už to nie je také cool priamočiare.

Napísať odpoveď pre Róbert Novotný Zrušiť odpoveď

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *