Malá čokoládová Spring REST aplikácia (List Milanovi)

Milý Milan, chcel si vedieť, ako vyzerá minimalistická RESTovská aplikácia postavená na aplikačnom rámci Spring Boot.

Tu je.

Predovšetkým, zíde sa ti Maven. Nielenže sa vysporiada so závislosťami v Springu, ale dá ti k dispozícii fajnový plugin pre Jetty, v ktorom bude spúšťanie servera vecou na 10 znakov.

Závislosti

Začni teda POMkom, ktorý oddeď od rodičovského POM súboru a zároveň dodaj závislosť pre podporu webu a modulu Spring Web MVC.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.1.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

Element <parent> vďaka dedičnosti dodá do nášho projektu množstvo užitočných závislostí a pluginov, vďaka ktorým sa spúšťanie projektu výrazne zjednoduší.

Nastavenie Springu a jadra webu

Základná trieda nášho projektu bude obsahovať metódu main() a anotáciu @SpringBootApplication, ktorá plní množstvo úloh popísaných nižšie.

Kód, ktorý využijeme bude nasledovný:

package sk.upjs.ics.novotnyr.chocolate;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ApplicationConfiguration {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationConfiguration.class, args);
    }
}

Anotácia @SpringBootApplication plní viacero úloh. Pre jednoduchosť ti spomeniem len to, že takto anotovaná trieda predstavuje vstupný bod aplikácie založenej na Spring Boote. Okrem toho ešte nastaví automatické vyhľadávanie REST endpointov (kontrolérov) a ich registráciu v Springu.

Nastavenie kontroléra

Kontrolér je trieda, ktorej metódy budú obsluhovať URL adresy pre RESTovské požiadavky.

Minimalistický kontrolér môže vyzerať takto:

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/chocolates")
public class ChocolateController {
    private List<Chocolate> chocolates = new CopyOnWriteArrayList<Chocolate>();

    public ChocolateController() {
        chocolates.add(new Chocolate("lindt", "Lindt", 72));
        chocolates.add(new Chocolate("choc-o-crap", "Choc'o'crap", 10));
        chocolates.add(new Chocolate("brownella", "Brownella", 52));
    }

    @RequestMapping
    public List<Chocolate> list() {
        return chocolates;
    }
}

Anotácie kontroléra

Začnime zhora: @RestController znamená, že táto trieda predstavuje kontrolér pre REST požiadavky, že návratové hodnoty jej metód budú automaticky serializované do výstupu pre klienta (prehliadač), a že sa má automaticky zaregistrovať v springovskom kontexte pri pátraní spôsobenom anotáciou @SpringBootApplication.

Druhá anotácia, @RequestMapping hovorí, že základná prípona v URL adrese pre tento kontrolér bude /chocolates.

V konštruktore si vytvoríš a naplníš inštanciu zoznamu s ukážkovými dátami: a tento zoznam musí byť vláknovo bezpečný, pretože kontrolér v Springu bude singleton a budú k nemu pristupovať viaceré vlákna súčasne (zodpovedajúce súčasným HTTP požiadavkam).

Anotácie metódy pre GET

Pozri sa teraz na metódu list(), ktorá sa zavolá vo chvíli, keď klientská aplikácia navštívi adresu v duchu http://localhost:8080/chocolates, a to s príponou cesty /chocolates, tento druhý request mapping prevezme všeobecnejšiu špecifikáciu cesty z anotácie nad kontrolérom a použije ju. A prečo metóda HTTP GET? Tá je totiž implicitná.

Návratová hodnota metódy je zoznam čokolád, čo je bežný Java objekt. Pamätáš si však na Jacksona v CLASSPATHe? Vďaka nemu sa tento zoznam automagicky premení na JSONovský reťazec v HTTP odpovedi. (A Jackson nepotrebuje žiadnu špeciálnu konfiguráciu ani anotácie.)

Spustenie

Môžeš si to skúsiť: spusti mvn spring-boot:run a navštív http://localhost:8080/chocolates. Uvidíš odpoveď:

[{"id":"lindt","title":"Lindt","percentage":72},{"id":"choc-o-crap","title":"Choc'o'crap","percentage":10},{"id":"brownella","title":"Brownella","percentage":52}]

Anotácie metódy pre POST

Teraz si skúsme aj opačný postup: dodajme metódu, ktorá prijme JSONovský string a vytvorí novú entitu. (V REST filozofii pôjde o mapovanie na HTTP POST).

@RequestMapping(method = RequestMethod.POST)
public Chocolate add(@RequestBody Chocolate chocolate) {
    chocolates.add(chocolate);
    return chocolate;
}

Ako vidíš, opäť je to metóda anotovanú cez @RequestMapping. V tomto prípade však môžeš uviesť aj HTTP metódu, na ktorú má reagovať táto metóda v objekte.

Pozorovať môžeš ešte jednu anotáciu: parameter metódy objektu @RequestBody hovorí, že celé telo v HTTP požiadavke sa má automaticky namapovať na objekt typu Chocolate. A keďže máme v CLASSPATHe Jacksona, Spring sa automaticky postará o to, aby akákoľvek HTTP POST požiadavka, ktorá má uvedený Content-Type ako JSON (teda application/json), sa pomocou tohto mŕtveho speváka deserializovala na bežný čokoládový objekt.

Žiaľ, toto sa už nedá vyskúšať pomocou čistého browsera (ten funguje len cez HTTP GET). Na testovanie je najlepšie zobrať niektorý z pluginov prehliadača: napríklad Firefox má svoj Poster, či HTTP Requester. Dôležité je, že požiadavka musí ísť tiež na adresu http://localhost:8080/chocolates, a musí mať správny Content Type.

Len bokom ti podotknem, že metóda vracia Chocolate. To nie je povinné, ale podľa RESTovských zásad je užitočné, ak sa klientovi na požiadavku vráti nejaký neprázdny obsah: v tomto prípade, keďže sa hrajkáme, stačí vrátiť to, čo prišlo na vstupe: teda čokoládu.

Vylepšenie HTTP POSTu

Príklad by si mohol ešte vylepšiť. Hovorí sa, že ak sa úspešne podarí vytvoriť RESTovský resource, server má odpovedať so stavovým kódom HTTP 201 Created. Nie je nič ľahšie: stačí dodať nad metódu add() anotáciu @ResponseStatus s príslušným stavovým kódom:

@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.CREATED)
public Chocolate add(@RequestBody Chocolate chocolate) {

Metódy s parametrami URL adries

Zatiaľ si videl len dve metódy: jednu na získanie zoznamu všetkých čokolád a druhú na vytvorenie novej čokolády. Pri RESTe však často budeš potrebovať získať informácie o jedinej čokoláde. Podľa restovaných zásad sa takáto informácia o čokoláde Lindt namapuje napr. URL adresu:

http://localhost:8080/chocolates/lindt

Obslúžiť takéto volanie v REST kontroléri možno vytvorením novej metódy, ktorá zároveň dokáže inteligentne vytiahnuť z URL adresy identifikátor čokolády.

@RequestMapping("/{id}")
public Chocolate get(@PathVariable String id) {
    Chocolate chocolate = findById(id);
    if(chocolate == null) {
        throw new ChocolateNotFoundException();
    }
    return chocolate;
}

Opäť si všimni anotáciu @RequestMapping: tá teraz udáva už aj parameter /{id}. Všetko, čo je v kučeravých zátvorkách (id) udáva tzv. path parameter alebo path variable, teda premennú cesty. Ak klient navštívi adresu http://localhost:8080/chocolates/lindt, Spring sa snaží vypátrať metódu v kontroléri, ktorá dokáže obslúžiť požiadavku s touto URL, a používa pri tom anotácie @RequestMapping. Do úvahy pritom berie nielen anotáciu na kontroléri, ale aj na metóde, pričom jednotlivé prípony ciest sa snaží “zlepiť” dohromady.

Prípona URL v @RequestMappingu na kontroléri zlepená s hodnotou @RequestMappingu na metóde dá dohromady /chocolates/{id}.

Ak si chceš vyzdvihnúť hodnotu parametra cesty id v metóde, použi na to štandardný parameter metódy String id. Aby bolo jasné, že sa do parametra má napchať hodnota z URL adresy… tiež na to použiješ anotáciu. V tomto prípade pôjde o anotáciu @PathVariable.

Iste sa pýtaš, ako sa odhadne názov parametra. Spring používa vúdú, ktoré toto hádanie urobí automaticky (premenná cesty id sa namapuje na rovnomenný parameter metódy).

Zvyšok metódy by mal byť jasný: vrátime bežnú inštanciu čokolády serializovanú na JSON.

S jedinou špecialitou: tou je prípad, že sa čokoláda s daným ID nenájde.

Výnimky

Ako vidíš, v metódach kontroléra možno hádzať výnimky. REST však vôbec nepozná koncept výnimky (koniec koncov, klient môže byť implementovaný v hocijakom, aj neobjektovom jazyku.). Detekovať výnimočné stavy môžeme pomocou HTTP stavových kódov a vhodne navolenému obsahu v tele odpovede HTTP.

Ak voláš REST adresu pre získanie objektu, ktorý neexistuje, mala by sa zjaviť stará známa klasika: stavový kód 404 (Not found).

A ako tieto stavové kódy súvisia s výnimkami? Jednoducho. Ak si vytvoríš triedu pre vlastnú výnimku, dodáš nad ňu anotáciu @ResponseStatus (áno, túto anotáciu som už raz použil vyššie), a v kóde metódy túto výnimku vyhodíš, Spring ju automaticky odchytí a vráti stavový kód HTTP, ktorý uvedieš v tejto anotácii.

Tu je príklad výnimky, ktorá sa prevedie na stavový kód 404:

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ChocolateNotFoundException extends RuntimeException {
    // no body needed
}

Záver

Záverom tohto listu ti už len prajem veľa šťastia a zdaru pri vlastných projektoch.

S priateľským pozdravom

   Róbert

P. S. Kompletné zdrojáky nájdeš na GitHube, v repozitári novotnyr/spring-boot-chocolate-rest-demo.

Ďalšie zdroje na čítanie

Pridaj komentár

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