Osa 1

Spring-sovelluskehys

Käytämme kurssilla Spring-sovellusperheen Spring Boot projektia web-sovellusten tekemiseen. Merkittävä osa web-sovellusten rakentamisesta perustuu valmiiden kirjastometodien käyttöön. Niiden avulla määritellään (1) mihin osoitteeseen tulevat pyynnöt käsitellään ja (2) mitä pyynnölle tulee tehdä. Spring Boot sisältää oletuksena myös web-palvelimen, jolloin ohjelmoijan tulee keskittyä vain palvelinlogiikan eli pyyntöjen käsittelyn toteuttamiseen.

Tutustutaan ensin Spring-sovelluskehyksen oleellisimpiin osiin, jonka jälkeen toteutamme ensimmäiset web-sovellukset Spring Bootin avulla.

Inversion of Control ja Dependency Injection

Inversion of Control on ohjelmistokehyksissä esiintyvä periaate, missä vastuuta ohjelman osien luomisesta sekä ohjelman osien välisestä kommunikaatiosta siirretään ohjelmistokehykselle. Tämä tarkoittaa käytännössä sitä, että kontrolli luotavista asioista sekä ohjelman suorituksesta on sovelluskehyksen vastuulla. Tällöin ohjelmoijan ei tarvitse kiinnittää huomiota kaikkiin yksityiskohtiin. Samalla toisaalta ohjelman suorituksen ymmärtäminen vaatii jonkinlaista ymmärrystä sovelluskehyksen toiminnasta.

Spring-sovelluskehyksessä Inversion of Control näkyy mm. siinä, että ohjelmoija toteuttaa sovelluskehyksen puitteissa luokkia, mutta ei esimerkiksi luo niistä olioita. Olioiden luominen on pääosin Spring-sovelluskehyksen vastuulla.

Dependency Injection on taas suunnittelumalli, missä riippuvuudet injektoidaan sovellukseen. Yksinkertaisimmillaan tämä tarkoittaa sitä, että luokkien oliomuuttujia ei luoda esim. konstruktoreissa, vaan ne annetaan konstruktorin parametrina tai esimerkiksi setterin parametrina.

Spring-sovelluskehyksen tapauksessa Inversion of Control ja Dependecy Injection luovat yhdessä tilanteen, missä sovelluskehys luo luokista olioita ja injektoi ne sovelluksen käyttöön. Tämän avulla vähennetään olioiden turhia riippuvuuksia, mikä helpottaa esimerkiksi sovellusten testaamista.

Lue lisää aiheesta James Shoren blogista ja Martin Fowlerin artikkelista.

Spring Boot -projektin luominen

Uudet Spring Boot -projektit luodaan tyypillisesti Spring Initializr-sivulla, missä käyttäjä voi valita käännökseen käytettävän työvälineen (Maven, Gradle), projektin käyttämän ohjelmointikielen (Java, Kotlin, Groovy), Spring Bootin version, sekä projektin käyttämät komponentit (esim. tietokannnat, kirjautuminen, ...).

Käytämme kurssilla sovellusten kääntämiseen Mavenia ja ohjelmointikielenä Javaa. Käytössämme on Spring Bootin versio 2.1.3. Projektiemme käyttämät komponentit kasvavat projektiemme myötä.

Maven ja projektipohja

Käytämme tällä kurssilla Mavenia valmiiden kirjastojen noutamiseen sekä projektien hallintaan. Uusissa projekteissa kannattaa kuitenkin käyttää yllä mainittua Spring Initializr -palvelua, mutta kurssilla käytettävät tehtäväpohjat sisältävät tarvittavat riippuvuudet.

Kun Spring-sovellus on konfiguroitu oikein (esim. Spring Initializr -palvelun avulla), Spring-projektin käynnistäminen onnistuu komentoriviltä komennolla mvn spring-boot:run. Tällöin — kun käytössä on Spring Devtools-projekti — sovellus käynnistyy muutosten yhteydessä automaattisesti uudestaan.

Ohjelmat voi ladata myös NetBeansiin, jossa ne toimivat kuten muidenkin ohjelmointikurssien tehtävät.

Ensimmäinen palvelinohjelmisto

Ensimmäinen palvelinohjelmisto — tai sovelluksen koodi — voi tuntia aluksi hieman monimutkaiselta. Sovellus, joka käynnistää palvelimen ja palauttaa käyttäjälle selaimen kautta tarkasteltuna tekstin "Hei Maailma!", näyttää seuraavalta.

package heimaailma;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@SpringBootApplication
@Controller
public class HeiMaailmaController {

    @GetMapping("*")
    @ResponseBody
    public String home() {
        return "Hei Maailma!";
    }

    public static void main(String[] args) throws Exception {
        SpringApplication.run(HeiMaailmaController.class, args);
    }
}

Luokka sisältää sekä sovelluskehyksen käynnistämiseen tarvittavan main-metodin että pyyntöjen käsittelyyn käytettävän home-metodin. Pilkotaan sovellus pienempiin osiin ja eriytetään pyyntöjä vastaanottava luokka ja sovelluksen käynnistämiseen käytettävä luokan toisistaan.

package heimaailma;

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

@SpringBootApplication
public class HeiMaailmaApplication {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(HeiMaailmaApplication.class, args);
    }
}

Spring Boot -sovellukset tarvitsevat käynnistyäkseen main-metodin, jossa kutsutaan SpringApplication-luokan run-metodia. Metodille annetaan parametrina luokka, joka sisältää @SpringBootApplication-annotaation — annotaatiota käytetään sovelluksen konfigurointiin; tässä mennään oletusasetuksilla.

Sovelluksen käynnistäminen etsii luokkia, joita se lataa käyttöönsä. Luokat on merkitty esim @Controller-annotaatiolla, mikä kertoo luokan sisältävän palvelimelle tulevia pyyntöjä käsitteleviä metodeja.

Alla on esimerkki tällaisesta luokasta.

package heimaailma;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HeiMaailmaController {

    @GetMapping("*")
    @ResponseBody
    public String home() {
        return "Hei Maailma!";
    }
}

Pyyntöjä vastaanottava luokka HeiMaailmaController on merkitty @Controller-annotaatiolla. Tämän perusteella Spring-sovelluskehys tietää, että luokan metodit saattavat käsitellä selaimesta tehtyjä pyyntöjä ja Spring ottaa vastuun pyyntöjen ohjaamisesta luokan metodeille.

Luokalle HeiMaailmaController on määritelty metodi home, jolla on kaksi annotaatiota: @GetMapping ja @ResponseBody. Annotaation @GetMapping avulla määritellään kuunneltava polku sekä HTTP-protokollan pyyntötapa. Kaikki HTTP-protokollan GET-pyynnöt ohjataan kyseiselle metodille, koska annotaatiolle @GetMapping on lisäksi määritelty parametri "*". Tähden sijaan parametrina voisi määritellä myös esimerkiksi polun.

Annotaatio @ResponseBody kertoo sovelluskehykselle, että metodin vastaus tulee näyttää vastauksena sellaisenaan.

Loading

Palvelinohjelmiston polut

Sovellus kuuntelee kaikkia palvelinohjelmistoon tulevia pyyntöjä jos pyyntöjen käsittelyyn tehty metodi on annotoitu @GetMapping-annotaatiolla, jolle on asetettu parametriksi "*". Käytännössä @GetMapping-annotaation parametrilla määritellään polku, johon palvelimelle tulevat pyynnöt voidaan ohjata. Tähdellä ilmoitetaan, että kyseinen metodi käsittelee kaikki pyynnöt. Muiden polkujen määrittely on luonnollisesti myös mahdollista.


Antamalla @GetMapping-annotaation poluksi merkkijono "/salaisuus", kaikki web-palvelimen osoitteeseen /salaisuus tehtävät pyynnöt ohjautuvat metodille, jolla kyseinen annotaatio on. Allaolevassa esimerkissä määritellään polku /salaisuus ja kerrotaan, että polkuun tehtävät pyynnöt palauttavat merkkijonon "Kryptos".

// pakkaus ja importit

@Controller
public class SalaisuusController {

    @GetMapping("/salaisuus")
    @ResponseBody
    public String home() {
        return "Kryptos";
    }
}
Loading

Yhteen ohjelmaan voi määritellä useampia polkuja. Jokainen polku käsitellään omassa metodissaan. Alla olevassa esimerkissä pyyntöjä vastaanottavaan luokkaan on määritelty kolme erillistä polkua, joista jokainen palauttaa käyttäjälle merkkijonon.

// pakkaus ja importit

@Controller
public class PolkuController {

    @GetMapping("/path")
    @ResponseBody
    public String path() {
        return "Polku (path)";
    }

    @GetMapping("/route")
    @ResponseBody
    public String route() {
        return "Polku (route)";
    }

    @GetMapping("/trail")
    @ResponseBody
    public String trail() {
        return "Polku (trail)";
    }
}
Loading

Pyynnön parametrit

Palvelimelle voi lähettää tietoa pyynnön parametreina. Tutustutaan ensin tapaan, missä pyynnön parametrit lisätään osoitteeseen. Esimerkiksi pyynnössä http://localhost:8080/salaisuus?onko=nauris on parametri nimeltä onko, jonka arvoksi on määritelty arvo nauris.

Parametrien lisääminen pyyntöön tapahtuu lisäämällä osoitteen perään kysymysmerkki, jota seuraa parametrin nimi, yhtäsuuruusmerkki ja parametrille annettava arvo. Pyynnössä tuleviin parametreihin pääsee käsiksi @RequestParam-annotaation avulla.

Allaolevan esimerkin sovellus tervehtii kaikkia pyynnön tekijöitä. Ohjelma käsittelee polkuun /hei tulevia pyyntöjä ja palauttaa niihin vastauksena tervehdyksen. Tervehdykseen liitetään pyynnössä tulevan nimi-parametrin arvo.

package parametrit;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TervehtijaController {

    @GetMapping("/hei")
    @ResponseBody
    public String tervehdi(@RequestParam String nimi) {
        return "Hei " + nimi + ", mitä kuuluu?";
    }
}

Nyt esimerkiksi osoitteeseen http://localhost:8080/hei?nimi=Ada tehtävä pyyntö saa vastaukseksi merkkijonon Hei Ada, mitä kuuluu?.

Loading

Jos parametreja on useampia, erotellaan ne toisistaan &-merkillä. Alla olevassa osoitteessa on kolme parametria, eka, toka ja kolmas, joiden arvot ovat 1, 2 ja 3 vastaavasti.

http://localhost:8080/salaisuus?eka=1&toka=2&kolmas=3

Useampaa parametria käsittelevän sovelluksen saa toteutettua erottamalla parametrit pilkuilla toisistaan. Yllä olevan osoitteen saisi käsiteltyä esimerkiksi seuraavalla tavalla.

// pakkaus ja importit

@Controller
public class SalaisuusController {

    @GetMapping("/salaisuus")
    @ResponseBody
    public String vastaa(@RequestParam String eka,
                         @RequestParam String toka,
                         @RequestParam String kolmas) {
        return "eka: " + eka + ", toka: " + toka + ", kolmas: " + kolmas;
    }
}
Loading

Mikäli parametreja ei tunneta, saa pyynnössä olevat parametrit saa käyttöön mm. @RequestParam-annotaatiolla, mitä seuraa Map-tietorakenne. Allaolevassa esimerkissä pyynnön parametrit asetetaan Map-tietorakenteeseen, jonka jälkeen kaikki pyyntöparametrien avaimet palautetaan kysyjälle.

// pakkaus ja importit

@Controller
public class PyyntoParametrienNimetController {

    @GetMapping("/nimet")
    @ResponseBody
    public String nimet(@RequestParam Map<String, String> parametrit) {
        return parametrit.keySet().toString();
    }
}

Tämä parametrien käsittely voi tuntua aluksi magialta. Todellisuudessa kuitenkin sovelluskehykseen on toteutettu merkittävä määrä logiikkaa, jonka perusteella pyynnössä olevat parametrit tunnistetaan ja lisätään pyynnön käsittelyyn tarkoitetun metodin parametreiksi.

Loading

Parametrien tyypit voidaan määritellä pyynnön käsittelevään metodiin. Mikäli tiedämme, että metodi saa parametrinaan kokonaislukumuotoisen arvon, voidaan se käsitellä määritellä kokonaisluvuksi. Esimerkiksi nimen ja iän vastaanottava metodi määriteltäisiin seuraavalla tavalla.

@GetMapping("/tervehdi")
@ResponseBody
public String tervehdi(@RequestParam String nimi, @RequestParam Integer ika) {
    return "Hei " + nimi + ", olet " + ika + " vuotta vanha.";
}

Huom! Yllä kokonaislukuparametri määritellään Integer-tyyppisenä. Tämä johtuu siitä, että tällöin arvo voi periaatteessa olla null. Alkeislukutyyppiseen int-muuttujaan ei voi asettaa null-arvoa, mikä voisi näkyä hyvin kryptisenä virheviestinä.

Loading
Loading
:
Loading interface...
:
Loading interface...

Kirjaudu sisään nähdäksesi tehtävän.

Pääsit aliluvun loppuun! Jatka tästä seuraavaan osaan: