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
- Stiahnime si distribúciu z projektových stránok,
- rozbaľme ju do vhodného adresára (napr. do
C:\java\spring-boot
), a pridajme cestu k podadresáru
bin
do systémovej premennejPATH
: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 CLASSPATH
e 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:
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 :)
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.