Nakopnite MVC webaplikáciu Spring Boot-om

Na turboštart springáckej aplikácie máme Spring Roo. Alebo Grails. Alebo… nový a svieži Spring Boot. Nie je to len zbytočný tretí hasák v škatuli s náradím?

Spring Boot nemá ďalekosiahle ciele, a netúži vygenerovať celú aplikáciu pomocou piatich príkazov z konzoly. Jeho cieľom je len veľmi rýchlo pripraviť infraštruktúrne základy, na ktorých potom sami ručne postavíte normálnu bežnú aplikáciu. Tu si ukážeme klasickú Spring MVC aplikáciu.

Ponúka dva režimy operácie:

  • napíšeme jednoduchý Groovy skript s bežnými kontrolérmi a spustíme ho z príkazového riadku
  • použijeme správcu závislostí — napr. Maven alebo Gradle — a naštartujeme projekt z pom.xml či podobného súboru.

V tomto článku si ukážeme prvý prístup.

Inštalácia Spring Boot

  1. Stiahnime si distribúciu z projektových stránok,
  2. rozbaľme ju do vhodného adresára (napr. do C:\java\spring-boot),
  3. a pridajme cestu k podadresáru bin do systémovej premennej PATH:

    SET PATH=%PATH%;C:\java\spring-boot\bin
    

Kontrolér zdraviaci svet!

Ahoj-svetový kontrolér vytvoríme veľmi jednoducho:

@Controller
@RequestMapping("/")
class HelloController {
    @RequestMapping
    @ResponseBody
    def hello() { "Hello World" }        
}

Áno, to je celý obsah súboru HelloController.groovy. Vďaka Groovy je syntax omnoho hustejšia a vďaka Spring Boot máme automaticky naimportované základné triedy.

Súbor môžeme spustiť cez

spring run HelloController.groovy

Prvé spustenie bude trvať dlhšie: Boot totiž potrebuje natiahnuť všetky závislosti. Po chvíľke sa obveselíme veľkým billboardom nasledovaným menej estetickým logom:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v0.5.0.M1)

2013-08-14 15:26:57.031  INFO 4112 --- [       runner-0] o.s.boot.SpringApplication               : Starting application on RN-PC with PID 4112 (C:\Users\rn\.groovy\grapes\org.springframework.boot\spring-boot\jars\spring-boot-0.5.0.M1.jar started by rn)
2013-08-14 15:26:57.069  INFO 4112 --- [       runner-0] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1a15ee0b: startup date [Wed Aug 14 15:26:57 CEST 2013]; root of context hierarchy
2013-08-14 15:26:57.604  INFO 4112 --- [       runner-0] org.eclipse.jetty.server.Server          : jetty-8.1.9.v20130131
2013-08-14 15:26:57.638  INFO 4112 --- [       runner-0] /                                        : Initializing Spring embedded WebApplicationContext
2013-08-14 15:26:57.639  INFO 4112 --- [       runner-0] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 570 ms
2013-08-14 15:26:57.714  INFO 4112 --- [       runner-0] /                                        : Initializing Spring FrameworkServlet 'dispatcherServlet'
2013-08-14 15:26:57.714  INFO 4112 --- [       runner-0] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2013-08-14 15:26:57.740  WARN 4112 --- [       runner-0] o.s.w.s.r.ResourceHttpRequestHandler     : Locations list is empty. No resources will be served
2013-08-14 15:26:57.751  INFO 4112 --- [       runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2013-08-14 15:26:57.852  INFO 4112 --- [       runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.Object HelloController.hello()
2013-08-14 15:26:57.875  INFO 4112 --- [       runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2013-08-14 15:26:57.875  INFO 4112 --- [       runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/resources/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2013-08-14 15:26:57.879  INFO 4112 --- [       runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler]
2013-08-14 15:26:58.059  INFO 4112 --- [       runner-0] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 344 ms
2013-08-14 15:26:58.087  INFO 4112 --- [       runner-0] o.e.jetty.server.AbstractConnector       : Started SelectChannelConnector@0.0.0.0:8080
2013-08-14 15:26:58.122  INFO 4112 --- [       runner-0] o.e.jetty.server.AbstractConnector       : Started SelectChannelConnector@0.0.0.0:8080

Automaticky sa spustí server a rovno môžeme navštíviť:

http://localhost:8080/

A obdivovať v prehliadači Hello World!

Za oponou

Spring Boot automaticky:

  • naštartoval server Jetty,
  • vytvoril springácky DispatcherServlet so štandardnými nastaveniami,
  • popri ňom vytvoril štandardný springácky webový aplikačný kontext
  • automaticky zdetekoval všetky triedy v Groovy skripte a zaviedol ich do aplikačného kontextu ako beany

Ešte predtým všetkým dotiahol závislosti s použitím Groovy Grapes a uložil ich do lokálneho repozitára v používateľovom adresári, v .groovy\grapes.

Kontrolér s JSONom

Spring Boot umožňuje používať v skriptoch celý repertoár MVC modulu. Potrebujeme kontrolér, čo bude komunikovať JSONom? Je to jednoduché!

@Grab(group='org.codehaus.jackson', module='jackson-mapper-asl', version='1.9.13')

@Controller
@RequestMapping("/chocolates")
class ChocolateController {
    def chocolates = [ 
            [ id: 1, name: "Lindt", percentage: "70%" ],
            [ id: 2, name: "Liedl", percentage: "15%" ]
    ]

    @RequestMapping
    @ResponseBody
    def getAll() {
        chocolates
    }
}

Celý kontrolér namapujeme na URL s príponou /chocolates. Jediná metóda bude vracať zoznam čokolád, čiže objekt typu java.util.List.

Samozrejme, ten sa musí akýmsi magickým spôsobom serializovať do JSONu. Opäť prichádza na rad štandardná spring-MVC mágia: ak sa v CLASSPATHe projektu nájde knižnica Jackson, typické objekty z metód kontroléra sa automaticky zaserializujú do JSONu.

Knižnicu zavedieme s využitím Groovy anotácie @Grab pre závislosti.

Spusťme teda:

spring run ChocolateController.groovy

Adresa pre kontrolér bude:

http://localhost:8080/chocolates

V prehliadači uvidíme:

[{"id":1,"name":"Lindt","percentage":"70%"},{"id":2,"name":"Liedl","percentage":"15%"}]

Automatická aktualizácia kontextu

Ak potrebujeme upraviť kontrolér, musíme po zmenách zastaviť bežiaci server (cez Ctrl-C) a následne ho spustiť nanovo cez spring run. Lepšou možnosťou je zapnúť automatické sledovanie zmien:

spring run ChocolateController.groovy --watch

Boot potom bude sledovať zmeny v súbore a podľa potreby reštartovať celú webaplikáciu v Jetty.

Metóda pre jednu čokoládu

Dodajme si ešte jednu metódu pre získanie jedinej čokolády. Tu využijeme klasické mapovania častí URL adries, čo je známa vec z RESTu:

@RequestMapping("/{id}")
@ResponseBody
def get(@PathVariable long id) {
    def chocolate = chocolates.find { it.id == id }
    if(chocolate == null) {
        throw new ResourceNotFoundException(resourceId: id)
    }
    chocolate
}    

Pokiaľ navštívime:

http://localhost:8080/chocolate/1

uvidíme prvú čokoládu, teda Lindt.

Čo s neexistujúcimi čokoládami? Z pohľadu používateľa by sme chceli vidieť stavový kód HTTP 404 (Not Found), ale z pohľadu metódy sa tým nechceme zapodievať.

Vyhodíme teda vlastnú výnimku ResourceNotFoundException, ktorú zadeklarujeme v skripte pod triedu kontroléra. Dokonca jej môžeme nahodiť springovskú anotáciu @ResponseStatus, v ktorej povieme, aký stavový kód sa má vrátiť, ak takáto výnimka nastane. Spring MVC sa sám postará o špinavé detaily.

@ResponseStatus(value=HttpStatus.NOT_FOUND)
class ResourceNotFoundException extends RuntimeException {
    def resourceId
}

Ak teraz navštívime napríklad:

http://localhost/chocolates/10000

Spring MVC vyhodí stavový kód 404 a zabudovaná Jetty vypíše štandardnú hlášku:

HTTP ERROR 404

Problem accessing /chocolates/10000. Reason:

    Not Found

Powered by Jetty://

Vylepšiť to môžeme metódou v kontroléri, ktorú označíme ako exception handler,teda obsluhovač konkrétnej výnimky:

@ExceptionHandler(ResourceNotFoundException)
@ResponseBody
def onResourceNotFound(def exception) {
    [ code : 404, id : exception.resourceId ]
}

V tejto metóde namiesto HTML vrátime kultúrny JSON, s ktorým sa môže klientská aplikácia vysporiadať.

Máme to

Ani sme sa nenazdali, a máme hotový kontrolér, ktorý je dokonca takmer RESTovský. Samozrejme, nič nebráni deklarovať ich v skripte viacero kontrolérov — aj keď pozor, lebo od istého momentu môže skript pripomínať začiatky v PHP.

Rovnako môžeme do skriptu nahádzať aj triedy biznis logiky: s anotáciami @Component, či @Service, ktoré sa automaticky odhalia a nahádžu do aplikačného kontextu. Následne ich môžeme prepájať klasickým spôsobom cez @Autowired.

Spring Boot tak umožňuje naozaj extrémne rýchlo nakopnúť jednoduchý prototyp webovej aplikácie.

Odkazy

Celý kód kontroléra:

2 thoughts on “Nakopnite MVC webaplikáciu Spring Boot-om

  1. Vyzerá to veľmi zaujímavo a pomerne jednoducho, len akosi si to neviem predstaviť na komplexnejšie webové aplikácie a integráciu s mnohými inými knižnicami. Ináč Váš blog sledujem stále, pretože prináša veľmi zaujímavé články, ktoré si rád prečítam & vyskúšam. Len tak ďalej :)

  2. Toto je len jeden z dvoch spôsobov použitia: druhý umožňuje zaviesť jedinú závislosť do Maven POMu a bootnuť tak regulárnu Spring/Java aplikáciu. O tom mám v úmysle ešte jeden článok.

Pridaj komentár

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