Spring-sovelluskehys
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.
Spring Boot -projektin luominen
Käytämme kurssilla sovellusten kääntämiseen Mavenia ja ohjelmointikielenä Javaa. Käytössämme on Spring Bootin versio 2.3.2. Projektiemme käyttämät komponentit kasvavat projektiemme myötä.
Maven ja projektipohja
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 tuntua 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ä luokka 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.
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";
}
}
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)";
}
}
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
.
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?
.
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;
}
}
Mikäli parametreja ei tunneta, saa pyynnössä olevat parametrit 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.
Parametrien tyypit voidaan määritellä pyynnön käsittelevään metodiin. Mikäli tiedämme, että metodi saa parametrinaan kokonaislukumuotoisen arvon, voidaan se 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ä.
:
Log in to view the quiz