1. Wprowadzenie
Plik PDF tego dokumentu jest dostępny pod nazwą |TUTAJ|.
Przykłady zawarte w tym dokumencie są dostępne pod numerem |TUTAJ|.
W niniejszym dokumencie zamierzamy przedstawić, posługując się przykładami, istotne pojęcia związane z Spring MVC – frameworkiem internetowym napisanym w języku Java, który zapewnia środowisko do tworzenia aplikacji internetowych zgodnie z modelem MVC (Model – Widok – Kontroler). Spring MVC stanowi gałąź ekosystemu Spring [http://projects.spring.io/spring-framework/]. Przedstawiamy również silnik widoków Thymeleaf [http://www.thymeleaf.org/].
Kurs ten jest przeznaczony dla czytelników posiadających solidną znajomość języka Java. Znajomość programowania stron internetowych nie jest wymagana.
Chociaż niniejszy dokument jest szczegółowy, prawdopodobnie nie jest kompletny. Spring to ogromny framework o wielu rozgałęzieniach. Aby pogłębić wiedzę na temat Spring MVC, można skorzystać z następujących źródeł:
- dokument referencyjny frameworka Spring [http://docs.spring.io/spring/docs/current/spring-framework-reference/pdf/spring-framework-reference.pdf];
- liczne samouczki dotyczące Springa można znaleźć na stronie URL [http://spring.io/guides]
- strona [developpez.com] poświęcona Springowi [http://spring.developpez.com/].
Dokument został napisany w taki sposób, aby można go było czytać bez konieczności korzystania z komputera. Dlatego zamieszczono w nim wiele zrzutów ekranu.
1.1. Sources
Niniejszy dokument opiera się na dwóch głównych źródłach:
- [Wprowadzenie do frameworka ASP.NET MVC na przykładach (2013)]. Spring MVC oraz ASP.NET MVC to dwa podobne frameworki, przy czym ten drugi powstał znacznie później niż pierwszy. Aby móc porównać oba frameworki, zastosowałem tę samą strukturę, co w dokumencie dotyczącym ASP.NET i MVC;
- dokument dotyczący ASP.NET MVC nie zawiera na razie (grudzień 2014 r.) studium przypadku z wykorzystaniem tego rozwiązania. Wykorzystałem tutaj studium przypadku z dokumentu [Przykład architektury klient-serwer – AngularJS 1.x / Spring 4 (2014)], które zmodyfikowałem w następujący sposób:
- przykład z praktyki w dokumencie [Tutoriel AngularJS / Spring 4] dotyczy aplikacji klient-serwer, w której serwerem jest usługa internetowa / jSON zbudowana przy użyciu Springa MVC, a klientem – klient AngularJS,
- w niniejszym dokumencie ponownie wykorzystujemy tę samą usługę internetową / jSON, ale klientem jest dwuwarstwowa aplikacja internetowa [client jQuery] / [service web / jSON];
Oprócz tych źródeł szukałem w Internecie odpowiedzi na moje pytania. Szczególnie pomocna okazała się strona [http://stackoverflow.com/].
1.2. Wykorzystane narzędzia
Poniższe przykłady zostały przetestowane w następującym środowisku:
- komputer z systemem Windows 8.1 Pro 64-bitowym;
- JDK 1.8;
- IDE Spring Tool Suite 3.6.3 (patrz punkt 9.3);
- przeglądarka Chrome (inne przeglądarki nie były używane);
- rozszerzenie Chrome [Advanced Rest Client] (patrz punkt 9.6);
Uwaga na wersję JDK 1.8. Jedna z metod przedstawionych w studium przypadku wykorzystuje metodę z pakietu [java.lang] w Javie 8.
Wszystkie przykłady to projekty Maven, które można otwierać zarówno w Eclipse, jak i w NetBeans. W dalszej części zrzuty ekranu pochodzą z Spring Tool Suite, odmiany środowiska Eclipse.
1.3. Przykłady
Przykłady są dostępne pod adresem URL [http://tahe.developpez.com/java/springmvc-thymeleaf] w postaci pliku zip do pobrania.
![]() |
Aby załadować wszystkie projekty do STS, należy postępować w następujący sposób:
![]() |
![]() |
- w pliku [1-3] zaimportuj projekty Maven;
![]() |
- w [4] należy wskazać folder z przykładami;
- w [5], wybierz wszystkie projekty z folderu;
- w [6], zatwierdź;
- w [7]: zaimportowane projekty;
1.4. Rola Spring MVC w aplikacji internetowej
Umieśćmy Spring MVC w kontekście tworzenia aplikacji internetowej. Najczęściej będzie ona oparta na architekturze wielowarstwowej, takiej jak poniższa:
![]() |
- warstwa [Web] stanowi warstwę kontaktową z użytkownikiem aplikacji internetowej. Użytkownik ten wchodzi w interakcję z aplikacją internetową za pośrednictwem stron internetowych wyświetlanych w przeglądarce. To właśnie w tej warstwie znajduje się Spring MVC i wyłącznie w tej warstwie;
- warstwa [métier] realizuje reguły biznesowe aplikacji, takie jak obliczanie wynagrodzenia lub faktury. Warstwa ta wykorzystuje dane pochodzące od użytkownika za pośrednictwem warstwy [Web] oraz dane z warstw SGBD za pośrednictwem warstwy [DAO];
- warstwa [DAO] (obiekty dostępu do danych), warstwa [ORM] (mapowanie obiektowo-relacyjne) oraz sterownik JDBC zarządzają dostępem do danych z warstwy SGBD. Warstwa [ORM] stanowi pomost między obiektami obsługiwanymi przez warstwę [DAO] a wierszami i kolumnami tabel w relacyjnej bazie danych. W tym przypadku wykorzystamy bibliotekę Hibernate o nazwie ORM. Specyfikacja o nazwie JPA (Java Persistence API) pozwala na abstrakcję od używanego ORM, o ile ten ostatni implementuje te specyfikacje. Tak jest w przypadku Hibernate i innych implementacji Java. Od tej pory będziemy więc nazywać warstwę ORM warstwą JPA;
- integracja warstw odbywa się za pośrednictwem frameworka Spring;
Większość przykładów podanych poniżej będzie wykorzystywać tylko jedną warstwę, warstwę [Web]:
![]() |
Niniejszy dokument zakończy się jednak stworzeniem wielowarstwowej aplikacji internetowej:
![]() |
Przeglądarka połączy się z aplikacją [Web1] zaimplementowaną przez Spring MVC / Thymeleaf, która będzie pobierać dane z serwisu internetowego [Web2], również zaimplementowanego przy użyciu Springa MVC. Ta druga aplikacja internetowa będzie korzystać z bazy danych.
1.5. Model rozwoju Spring MVC
Spring MVC implementuje model architektury zwany MVC (Model – Widok – Kontroler) w następujący sposób:
![]() |
Przetwarzanie żądania klienta przebiega w następujący sposób:
- żądanie – żądane obiekty URL mają postać http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&.... [Front Controller] wykorzystuje plik konfiguracyjny lub adnotacje Java do „przekierowania” żądania do właściwego kontrolera i właściwej akcji w ramach tego kontrolera. W tym celu wykorzystuje pole [Action] z URL. Pozostała część URL i [/param1/param2/...] składa się z opcjonalnych parametrów, które zostaną przekazane do akcji. C w MVC to w tym przypadku ciąg znaków [Front Controller, Contrôleur, Action]. Jeśli żaden kontroler nie jest w stanie obsłużyć żądanej akcji, serwer WWW odpowie, że żądana akcja URL nie została znaleziona.
- Przetwarzanie
- wybrana akcja może wykorzystać parametry parami, które przekazała jej akcja [Front Controller]. Mogą one pochodzić z różnych źródeł:
- ze ścieżki [/param1/param2/...] pliku URL,
- z parametrów [p1=v1&p2=v2] z URL,
- z parametrów przesłanych przez przeglądarkę wraz z żądaniem;
- podczas przetwarzania żądania użytkownika akcja może wymagać warstwy [métier] [2b]. Po przetworzeniu żądania klienta może ono wywołać różne odpowiedzi. Klasycznym przykładem jest:
- strona błędu, jeśli żądanie nie mogło zostać poprawnie przetworzone
- w przeciwnym razie strona potwierdzenia
- akcja żąda wyświetlenia określonego widoku [3]. Widok ten wyświetli dane, które nazywamy modelem widoku. To właśnie litera M w MVC. Akcja utworzy ten model M o nazwie [2c] i zażąda wyświetlenia widoku V o nazwie [3];
- odpowiedź – wybrany widok V wykorzystuje model M utworzony przez akcję do zainicjowania dynamicznych części odpowiedzi HTML, którą musi wysłać do klienta, a następnie wysyła tę odpowiedź.
Teraz wyjaśnijmy związek między architekturą internetową MVC a architekturą warstwową. W zależności od definicji modelu te dwie koncepcje mogą być ze sobą powiązane lub nie. Weźmy na przykład jednowarstwową aplikację internetową Spring MVC:
![]() |
Jeśli zaimplementujemy warstwę [Web] przy użyciu Springa MVC, otrzymamy wprawdzie architekturę internetową MVC, ale nie będzie to architektura wielowarstwowa. W tym przypadku warstwa [web] zajmie się wszystkim: prezentacją, logiką biznesową oraz dostępem do danych. Zadania te będą realizowane przez akcje.
Rozważmy teraz wielowarstwową architekturę internetową:
![]() |
Warstwa [Web] może zostać zaimplementowana bez użycia frameworka i bez stosowania się do modelu MVC. Mamy wówczas do czynienia z architekturą wielowarstwową, ale warstwa internetowa nie implementuje modelu MVC.
Na przykład w środowisku .NET warstwa [Web]może być zaimplementowana przy użyciu ASP.NET i MVC, co daje architekturę warstwową z warstwą [Web] typu MVC. Po wykonaniu tej czynności można zastąpić tę warstwę ASP.NET MVC klasyczną warstwą ASP.NET (WebForms), zachowując pozostałe elementy (biznesową, DAO, ORM) bez zmian. Otrzymujemy wówczas architekturę warstwową z warstwą [Web], która nie jest już typu MVC.
W MVC stwierdziliśmy, że model M odpowiada widokowi V, c.a.d, czyli zbiorowi danych wyświetlanych przez widok V. Podano kolejną definicję modelu M dla MVC:
![]() |
Wielu autorów uważa, że to, co znajduje się po prawej stronie warstwy [Web], tworzy model M warstwy MVC. Aby uniknąć niejasności, można mówić o:
- o modelu domeny, gdy mamy na myśli wszystko, co znajduje się po prawej stronie warstwy [Web]
- o modelu widoku, gdy mamy na myśli dane wyświetlane przez widok V
W dalszej części termin „model M” będzie odnosił się wyłącznie do modelu widoku V.
1.6. Pierwszy projekt Spring MVC
Od tej pory będziemy pracować z pakietem Spring Tool Suite (IDE), czyli wersją środowiska Eclipse dostosowaną do potrzeb Springa. Strona [http://spring.io/guides] zawiera samouczki dla początkujących, które pomogą zapoznać się z ekosystemem Springa. Skorzystamy z jednego z nich, aby zapoznać się z konfiguracją Maven niezbędną dla projektu Spring MVC.
Uwaga: większość początkujących użytkowników nie zrozumie szczegółów projektu. Nie ma to jednak znaczenia. Szczegóły te zostaną wyjaśnione w dalszej części dokumentu. Wystarczy, że powtórzymy opisane czynności.
1.6.1. Projekt demonstracyjny
![]() |
- w [1] importujemy jeden z przewodników Spring;
![]() |
- w [2] wybieramy przykład [Serving Web Content];
- w [3] wybieramy projekt Maven;
- w [4] wybieramy ostateczną wersję przewodnika;
- w [5] zatwierdzamy;
- w pliku [6] – zaimportowany projekt;
Przyjrzyjmy się projektowi, a najpierw jego konfiguracji Maven.
1.6.2. Konfiguracja Maven
Plik [pom.xml] ma następującą treść:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-serving-web-content</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<properties>
<start-class>hello.Application</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
- wiersze 6–8: właściwości projektu Maven. Brakuje znacznika [<packaging>] określającego typ pliku generowanego przez kompilację Maven. W przypadku jego braku stosowany jest typ [jar]. Aplikacja jest zatem aplikacją konsolową, a nie aplikacją internetową, w przypadku której pakiet miałby typ [war];
- wiersze 10–14: projekt Maven ma projekt nadrzędny o nazwie [spring-boot-starter-parent]. To właśnie on definiuje większość zależności projektu. Mogą one być wystarczające, w którym to przypadku nie dodaje się żadnych nowych, lub mogą być niewystarczające, w którym to przypadku dodaje się brakujące zależności;
- wiersze 17–20: artefakt [spring-boot-starter-thymeleaf] zawiera biblioteki niezbędne dla projektu Spring o nazwie MVC, używanego wspólnie z silnikiem widoków o nazwie [Thymeleaf]. Ten artefakt zawiera bardzo dużą liczbę bibliotek, w tym biblioteki wbudowanego serwera Tomcat. To właśnie na tym serwerze będzie uruchamiana aplikacja;
Biblioteki dostarczane w ramach tej konfiguracji są bardzo liczne:
![]() | ![]() |
Powyżej widoczne są archiwa serwera Tomcat.
Spring Boot jest gałęzią ekosystemu Spring [http://projects.spring.io/spring-boot/]. Projekt ten ma na celu maksymalne ograniczenie konfiguracji projektów Spring. W tym celu Spring Boot dokonuje autokonfiguracji na podstawie zależności obecnych w ścieżce Classpath projektu. Spring Boot udostępnia wiele gotowych do użycia zależności. W ten sposób zależność [spring-boot-starter-thymeleaf] znaleziona w poprzednim projekcie Maven dostarcza wszystkie niezbędne zależności dla aplikacji Spring MVC korzystającej z silnika widoków [Thymeleaf]. Dzięki tym dwóm cechom:
- gotowe do użycia zależności;
- autokonfiguracja oparta na tych zależnościach i „rozsądnych” wartościach domyślnych, można bardzo szybko uzyskać działającą aplikację Spring MVC. Tak jest w przypadku projektu omawianego w niniejszym artykule;
1.6.3. Architektura aplikacji Spring MVC
Spring MVC realizuje model architektury zwany MVC (Model – Widok – Kontroler):
![]() |
Przetwarzanie żądania klienta przebiega w następujący sposób:
- żądanie – żądane URL mają postać http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... [Dispatcher Servlet] to klasa Springa, która przetwarza przychodzące URL. „Przekierowuje” ona URL do akcji, która ma ją przetworzyć. Akcje te są metodami określonych klas o nazwach [Contrôleurs]. Litera „C” w MVC oznacza tutaj ciąg znaków [Dispatcher Servlet, Contrôleur, Action]. Jeśli nie skonfigurowano żadnej akcji do przetworzenia przychodzącego URL, serwlet [Dispatcher Servlet] odpowie, że żądany URL nie został znaleziony (błąd 404 NOT FOUND);
- przetwarzanie
- wybrana akcja może wykorzystać parametry parami, które przekazała jej serwlet [Dispatcher Servlet]. Mogą one pochodzić z kilku źródeł:
- ze ścieżki [/param1/param2/...] serwletu URL,
- z parametrów [p1=v1&p2=v2] serwletu URL,
- z parametrów przesłanych przez przeglądarkę wraz z żądaniem;
- Podczas przetwarzania żądania użytkownika akcja może wymagać warstwy [metier] [2b]. Po przetworzeniu żądania klienta akcja ta może wywołać różne odpowiedzi. Klasycznym przykładem jest:
- strona błędu, jeśli żądania nie udało się poprawnie przetworzyć
- w przeciwnym razie strona potwierdzenia
- akcja żąda wyświetlenia określonego widoku [3]. Widok ten wyświetli dane, które nazywamy modelem widoku. To właśnie litera M w MVC. Akcja utworzy ten model M [2c] i zażąda wyświetlenia widoku V [3];
- odpowiedź – wybrany widok V wykorzystuje model M utworzony przez akcję do zainicjowania dynamicznych części odpowiedzi HTML, którą musi wysłać do klienta, a następnie wysyła tę odpowiedź.
Przyjrzyjmy się tym różnym elementom w analizowanym projekcie.
1.6.4. Kontroler C
![]() |
Zaimportowana aplikacja posiada następujący kontroler:
package hello;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class GreetingController {
@RequestMapping("/greeting")
public String greeting(@RequestParam(value="name", required=false, defaultValue="World") String name, Model model) {
model.addAttribute("name", name);
return "greeting";
}
}
- wiersz 8: adnotacja [@Controller] sprawia, że klasa [GreetingController] staje się kontrolerem Springa, co oznacza, że jej metody są zarejestrowane do obsługi URL. Kontroler Springa jest singletonem. Tworzony jest w jednym egzemplarzu;
- wiersz 11: adnotacja [@RequestMapping] wskazuje obiekt URL, który jest przetwarzany przez metodę, w tym przypadku obiekt URL [/greeting]. W dalszej części zobaczymy, że ten obiekt URL można skonfigurować i że możliwe jest pobranie tych parametrów;
- wiersz 12: metoda przyjmuje dwa parametry:
- [String name]: ten parametr jest inicjowany przez parametr o nazwie [name] w przetwarzanym zapytaniu, na przykład [/greeting?name=alfonse]. Ten parametr jest opcjonalny ([required=false]), a gdy go nie ma, parametr [name] przyjmie wartość „World” ([defaultValue="World"]),
- [Model model] jest modelem widoku. Jest on przekazywany jako pusty, a zadaniem akcji (metody greeting) jest jego wypełnienie. To właśnie ten model zostanie przekazany do widoku, który zostanie wyświetlony przez akcję. Jest to zatem model widoku;
- wiersz 13: wartość [name] jest umieszczana w modelu widoku. Klasa [Model] zachowuje się jak słownik;
- wiersz 14: metoda zwraca nazwę widoku, który ma wyświetlić skonstruowany szablon. Dokładna nazwa widoku zależy od konfiguracji [Thymeleaf]. W przypadku braku tej konfiguracji wyświetlanym widokiem będzie widok [/templates/greeting.html], a folder [templates] musi znajdować się w katalogu głównym ścieżki Classpath projektu;
Przyjrzyjmy się naszemu projektowi Eclipse:
![]() |
Foldery [src/main/java] i [src/main/resources] to foldery, których zawartość zostanie umieszczona w ścieżce Classpath projektu. W przypadku folderu [src/main/java] będą to skompilowane wersje kodu źródłowego Java. Z kolei zawartość folderu [src/main/resources] jest umieszczana w ścieżce Classpath bez żadnych modyfikacji. Widzimy zatem, że folder [templates] znajdzie się w ścieżce Classpath projektu [1].
Można to sprawdzić w oknie [Navigator] programu Eclipse dla projektu [2-3]. Katalog [target] powstaje w wyniku kompilacji (o nazwie build) projektu. Katalog [classes] stanowi katalog główny ścieżki Classpath. Widać, że znajduje się w nim katalog [templates].
1.6.5. Widok V
W pliku MVC widzieliśmy właśnie kontroler C i model widoku M. Widok V jest tutaj reprezentowany przez następujący plik [greeting.html]:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Getting Started: Serving Web Content</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>
- wiersz 2: przestrzeń nazw tagów Thymeleaf;
- wiersz 8: tag <p> (akapit) z atrybutem Thymeleaf. Atrybut [th:text] określa treść akapitu. Wewnątrz ciągu znaków znajduje się wyrażenie [${name}]. Oznacza to, że chcemy uzyskać wartość atrybutu [name] z szablonu widoku. Pamiętamy jednak, że atrybut ten został umieszczony w szablonie przez akcję:
model.addAttribute("name", name);
Pierwszy parametr określa nazwę atrybutu, a drugi jego wartość.
1.6.6. Wykonanie
![]() |
Klasa [Application.java] jest klasą wykonalną projektu. Jej kod wygląda następująco:
package hello;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- wiersz 11: klasa jest wykonywalna za pomocą metody [main], charakterystycznej dla aplikacji konsolowych. Klasa [SpringApplication] z wiersza 12 uruchomi serwer Tomcat znajdujący się w zależnościach i wdroży na nim usługę internetową;
- wiersz 4: widać, że klasa [SpringApplication] należy do projektu [Spring Boot];
- wiersz 12: pierwszy parametr to klasa konfigurująca projekt, drugi to ewentualne parametry;
- wiersz 8: adnotacja [@EnableAutoConfiguration] nakazuje Spring Boot skonfigurowanie projektu;
- wiersz 7: adnotacja [@ComponentScan] powoduje, że katalog zawierający klasę [Application] zostanie przeszukany w celu znalezienia komponentów Spring. Zostanie znaleziony komponent – klasa [GreetingController], która posiada adnotację [@Controller], dzięki czemu staje się komponentem Spring;
Uruchommy projekt:
![]() |
Otrzymujemy następujące logi konsoli:
- wiersz 13: serwer Tomcat uruchamia się na porcie 8080 (wiersz 12);
- wiersz 17: serwlet [DispatcherServlet] jest obecny;
- wiersz 20: wykryto metodę [hello.GreetingController.greeting] oraz metodę URL, która przetwarza metodę [/greeting];
Aby przetestować aplikację internetową, wywołujemy metodę URL oraz [http://localhost:8080/greeting]:
![]() | ![]() |
Warto przyjrzeć się nagłówkom wysyłanym przez serwer. W tym celu użyjemy rozszerzenia do przeglądarki Chrome o nazwie [Advanced Rest Client] (patrz punkt 9.6):
![]() |
- w przypadku [1] żądana jest metoda URL;
- w [2] zastosowano metodę GET;
- w [3] serwer poinformował, że wysyła odpowiedź w formacie HTML;
- w [4] – odpowiedź HTML;
- w [5] żądana jest ta sama odpowiedź URL, ale tym razem z POST;
- w [7] informacje są wysyłane do serwera w postaci [urlencoded];
- w [6] – parametr „name” wraz z jego wartością;
- w [8] przeglądarka informuje serwer, że przesyła mu informacje [urlencoded];
- w [9] – odpowiedź serwera HTML;
Aby zatrzymać aplikację:
![]() | ![]() | ![]() |
1.6.7. Tworzenie archiwum wykonywalnego
Możliwe jest utworzenie archiwum wykonywalnego poza środowiskiem Eclipse. Niezbędna konfiguracja znajduje się w pliku [pom.xml]:
<properties>
<start-class>hello.Application</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- wiersze 7–10 definiują wtyczkę, która utworzy archiwum wykonywalne;
- wiersz 2 określa klasę wykonywalną projektu;
Postępuje się w następujący sposób:
![]() |
- w [1]: uruchamia się cel Maven;
![]() |
- na [2]: istnieją dwa cele (goals): [clean] w celu usunięcia folderu [target] z projektu Maven oraz [package] w celu jego ponownego wygenerowania;
- w [3]: wygenerowany folder [target] zostanie umieszczony w tym folderze;
- w [4]: generowany jest cel;
Uwaga: aby generowanie przebiegło pomyślnie, plik JVM używany przez STS musi być plikiem JDK [Window / Preferences / Java / Installed JREs]:
![]() |
W logach wyświetlanych w konsoli ważne jest, aby pojawiła się wtyczka [spring-boot-maven-plugin]. To właśnie ona generuje archiwum wykonywalne.
W konsoli przechodzimy do wygenerowanego folderu:
gs-serving-web-content-complete\target>dir
...
Répertoire de D:\data\istia-1415\spring mvc\dvp\gs-serving-web-content-complete
\target
27/11/2014 17:07 <DIR> .
27/11/2014 17:07 <DIR> ..
27/11/2014 17:07 <DIR> classes
27/11/2014 17:07 <DIR> generated-sources
27/11/2014 17:07 13 419 551 gs-serving-web-content-0.1.0.jar
27/11/2014 17:07 3 522 gs-serving-web-content-0.1.0.jar.original
27/11/2014 17:07 <DIR> maven-archiver
27/11/2014 17:07 <DIR> maven-status
- wiersz 12: wygenerowany plik archiwum;
Archiwum to uruchamia się w następujący sposób:
gs-serving-web-content-complete\target>java -jar gs-serving-web-content-0.1.0.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.1.9.RELEASE)
2014-11-27 17:14:50.439 INFO 8172 --- [ main] hello.Application : Starting Application on Gportpers3 with PID 8172 (D:\data\istia-1415\spring mvc\dvp\gs-serving-web-content-complete\target\gs-serving-web-content-0.1.0.jar started by ST in D:\data\istia-1415\spring mvc\dvp\gs-serving-web-content-complete\target)
2014-11-27 17:14:50.491 INFO 8172 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@12f4ec3a: startup date [Thu Nov 27 17:14:50 CET 2014]; root of context hierarchy
Uwaga: należy wcześniej zatrzymać usługę internetową, która mogła zostać uruchomiona w Eclipse (patrz strona 17).
Teraz, gdy aplikacja internetowa jest uruchomiona, można ją wywołać za pomocą przeglądarki:
![]() |
1.6.8. Wdrożenie aplikacji na serwerze Tomcat
Chociaż Spring Boot jest bardzo wygodny w trybie programowania, aplikacja w środowisku produkcyjnym zostanie wdrożona na prawdziwym serwerze Tomcat. Oto jak to zrobić:
Zmodyfikuj plik [pom.xml] w następujący sposób:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-serving-web-content</artifactId>
<version>0.1.0</version>
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.9.RELEASE</version>
</parent>
<dependencies>
<!-- środowisko Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- generowanie pliku WAR -->
<!-- <zależność>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency> -->
</dependencies>
<properties>
<start-class>hello.Application</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
Zmiany należy wprowadzić w dwóch miejscach:
- wiersz 9: należy wskazać, że zamierzamy wygenerować archiwum WAR (Web ARchive);
- wiersze 24–28: należy dodać zależność od artefaktu [spring-boot-starter-tomcat]. Artefakt ten wprowadza wszystkie klasy Tomcata do zależności projektu;
- wiersz 27: artefaktem tym jest [provided], co oznacza, że odpowiednie archiwa nie zostaną umieszczone w wygenerowanym pliku WAR. Archiwa te będą bowiem znajdować się na serwerze Tomcat, na którym będzie działać aplikacja;
W rzeczywistości, jeśli przyjrzymy się aktualnym zależnościom projektu, zauważymy, że zależność [spring-boot-starter-tomcat] już istnieje:
![]() |
Nie ma zatem potrzeby dodawania jej do pliku [pom.xml]. Została ona skomentowana dla przypomnienia.
Ponadto należy skonfigurować aplikację internetową. W przypadku braku pliku [web.xml] odbywa się to za pomocą klasy dziedziczącej po [SpringBootServletInitializer]:
![]() |
Klasa [ApplicationInitializer] ma następującą postać:
package hello;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
public class ApplicationInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
- wiersz 6: klasa [ApplicationInitializer] rozszerza klasę [SpringBootServletInitializer];
- wiersz 9: metoda [configure] została ponownie zdefiniowana (wiersz 8);
- wiersz 10: podano klasę konfigurującą projekt;
Aby uruchomić projekt, można postępować w następujący sposób:
![]() |
- w [1] uruchamia się projekt na jednym z serwerów zarejestrowanych w IDE Eclipse;
- w [2] wybieramy powyżej [Tomcat v8.0];
Po wykonaniu tych czynności można wyświetlić URL [http://localhost:8080/gs-rest-service/greeting/?name=Mitchell] w przeglądarce:
![]() |
Uwaga: w zależności od wersji [tomcat] i [tc Server Developer] wykonanie tego polecenia może się nie powieść. Tak było na przykład w przypadku [Apache Tomcat 8.0.3 et 8.0.15]. W powyższym przykładzie używana była wersja Tomcat o numerze [8.0.9].
Wiemy już, jak wygenerować archiwum WAR. W dalszej części będziemy kontynuować pracę ze Spring Boot i jego wykonywalnym archiwum JAR.
1.7. Drugi projekt Spring MVC
1.7.1. Projekt demonstracyjny
![]() |
- w [1] importujemy jeden z przewodników Spring;
![]() |
- w [2] wybieramy przykład [Rest Service];
- w [3] wybieramy projekt Maven;
- w [4] wybieramy ostateczną wersję przewodnika;
- w [5] zatwierdzamy;
- w [6] – zaimportowany projekt;
Usługi internetowe dostępne za pośrednictwem standardów URL i dostarczające tekst jSON są często nazywane usługami REST (REpresentational State Transfer). W niniejszym dokumencie będę nazywał usługę, którą zamierzamy stworzyć, po prostu usługą internetową / jSON. Usługa jest określana jako Restful, jeśli spełnia określone zasady. Nie starałem się ich przestrzegać.
Przyjrzyjmy się teraz zaimportowanemu projektowi, a najpierw jego konfiguracji Maven.
1.7.2. Konfiguracja Maven
Plik [pom.xml] ma następującą treść:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-rest-service</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<start-class>hello.Application</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
- wiersze 6–8: właściwości projektu Maven. Brakuje znacznika [<packaging>] określającego typ pliku generowanego przez kompilację Maven. W przypadku jego braku stosowany jest typ [jar]. Aplikacja jest zatem aplikacją konsolową, a nie aplikacją internetową, w przypadku której pakiet miałby typ [war];
- wiersze 10–14: projekt Maven ma projekt nadrzędny o nazwie [spring-boot-starter-parent]. To właśnie on definiuje większość zależności projektu. Mogą one być wystarczające, w którym to przypadku nie dodaje się żadnych nowych, lub mogą być niewystarczające, w którym to przypadku dodaje się brakujące zależności;
- wiersze 17–20: artefakt [spring-boot-starter-web] zawiera biblioteki niezbędne dla projektu Spring o nazwie MVC, będącego serwisem internetowym, w którym nie ma generowanych widoków. Ten artefakt zawiera bardzo dużą liczbę bibliotek, w tym biblioteki wbudowanego serwera Tomcat. To właśnie na tym serwerze będzie uruchamiana aplikacja;
Biblioteki dostarczane w ramach tej konfiguracji są bardzo liczne:
![]() | ![]() |
Powyżej widoczne są trzy archiwa serwera Tomcat.
1.7.3. Architektura serwisu Spring [web / jSON]
Przypomnijmy, w jaki sposób Spring MVC implementuje model MVC:
![]() |
Przetwarzanie żądania klienta przebiega w następujący sposób:
- żądanie – żądane obiekty URL mają postać http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... [Dispatcher Servlet] to klasa Springa, która przetwarza przychodzące obiekty URL. „Przekierowuje” ona URL do akcji, która ma ją przetworzyć. Akcje te są metodami określonych klas o nazwach [Contrôleurs]. Litera „C” w MVC oznacza tutaj ciąg znaków [Dispatcher Servlet, Contrôleur, Action]. Jeśli nie skonfigurowano żadnej akcji do przetworzenia przychodzącego URL, serwlet [Dispatcher Servlet] odpowie, że żądany URL nie został znaleziony (błąd 404 NOT FOUND);
- przetwarzanie
- wybrana akcja może wykorzystać parametry parami, które przekazała jej serwlet [Dispatcher Servlet]. Mogą one pochodzić z różnych źródeł:
- ze ścieżki [/param1/param2/...] serwletu URL,
- z parametrów [p1=v1&p2=v2] serwletu URL,
- z parametrów przesłanych przez przeglądarkę wraz z żądaniem;
- podczas przetwarzania żądania użytkownika akcja może wymagać warstwy [metier] [2b]. Po przetworzeniu żądania klienta może ono wywołać różne odpowiedzi. Klasycznym przykładem jest:
- strona błędu, jeśli żądanie nie mogło zostać poprawnie przetworzone
- w przeciwnym razie strona potwierdzenia
- akcja żąda wyświetlenia określonego widoku [3]. Widok ten wyświetli dane, które nazywamy modelem widoku. To właśnie litera M w MVC. Akcja utworzy ten model M [2c] i zażąda wyświetlenia widoku V [3];
- odpowiedź – wybrany widok V wykorzystuje model M utworzony przez akcję do zainicjowania dynamicznych części odpowiedzi HTML, którą musi wysłać do klienta, a następnie wysyła tę odpowiedź.
W przypadku usługi internetowej / jSON powyższa architektura ulega niewielkiej modyfikacji:
![]() |
- w [4a] model, który jest klasą Java, jest przekształcany w ciąg znaków jSON przez bibliotekę jSON;
- w [4b] ten ciąg znaków jSON jest wysyłany do przeglądarki;
1.7.4. Kontroler C
![]() |
Zaimportowana aplikacja posiada następujący kontroler:
package hello;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
@RequestMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
- wiersz 9: adnotacja [@RestController] sprawia, że klasa [GreetingController] staje się kontrolerem Springa, co oznacza, że jej metody są zarejestrowane do obsługi obiektów typu URL. Widzieliśmy już podobną adnotację [@Controller]. Wynikiem działania metod tego kontrolera był typ [String], który stanowił nazwę widoku do wyświetlenia. Tutaj jest inaczej. Metody kontrolera typu [@RestController] zwracają obiekty, które są serializowane w celu wysłania do przeglądarki. Typ przeprowadzanej serializacji zależy od konfiguracji Springa MVC. W tym przypadku będą one serializowane do formatu jSON. To właśnie obecność biblioteki jSON wśród zależności projektu sprawia, że Spring Boot, poprzez autokonfigurację, skonfiguruje projekt w ten sposób;
- wiersz 14: adnotacja [@RequestMapping] wskazuje na URL, który jest przetwarzany przez metodę, w tym przypadku URL [/greeting];
- wiersz 15: adnotację [@RequestParam] wyjaśniliśmy już wcześniej. Wynikiem zwracanym przez metodę jest obiekt typu [Greeting].
- wiersz 12: liczba całkowita typu long o charakterze atomowym. Oznacza to, że obsługuje współbieżność dostępu. Kilka wątków może jednocześnie próbować zwiększyć wartość zmiennej [counter]. Odbędzie się to poprawnie. Wątek może odczytać wartość licznika dopiero wtedy, gdy wątek dokonujący modyfikacji zakończył swoją operację.
1.7.5. Model M
Model M wygenerowany przez poprzednią metodę to następujący obiekt [Greeting]:
![]() |
package hello;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
Transformacja jSON tego obiektu utworzy ciąg znaków {"id":n,"content":"tekst"}. Ostatecznie ciąg jSON wygenerowany przez metodę kontrolera będzie miał postać:
{"id":2,"content":"Hello, World!"}
lub
{"id":2,"content":"Hello, John!"}
1.7.6. Wykonanie
![]() |
Klasa [Application.java] jest klasą wykonywalną projektu. Jej kod wygląda następująco:
package hello;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Ten kod omówiliśmy już w poprzednim przykładzie.
1.7.7. Uruchomienie projektu
Uruchommy projekt:
![]() |
Otrzymujemy następujące logi konsoli:
- wiersz 13: serwer Tomcat uruchamia się na porcie 8080 (wiersz 12);
- wiersz 17: serwlet [DispatcherServlet] jest obecny;
- wiersz 20: wykryto metodę [GreetingController.greeting];
Aby przetestować aplikację internetową, wysyłamy żądanie do serwletu URL [http://localhost:8080/greeting]:
![]() | ![]() |
Otrzymujemy oczekiwany ciąg znaków jSON.
Uwaga: ten przykład nie zadziałał w przeglądarce wbudowanej w Eclipse.
Warto przyjrzeć się nagłówkom HTTP wysyłanym przez serwer. W tym celu użyjemy wtyczki do przeglądarki Chrome o nazwie [Advanced Rest Client] (patrz załączniki, punkt 9.6):
![]() |
- w [1], żądany plik URL;
- w [2] zastosowano metodę GET;
- w [3] – odpowiedź jSON;
- w [4] serwer poinformował, że wysyła odpowiedź w formacie jSON;
- w [5] żądana jest ta sama odpowiedź URL, ale tym razem z POST;
- w [7] informacje są wysyłane do serwera w postaci [urlencoded];
- w [6] – parametr „name” wraz z jego wartością;
- w [8] przeglądarka informuje serwer, że wysyła mu informacje [urlencoded];
- w [9] – odpowiedź serwera jSON;
1.7.8. Tworzenie archiwum wykonywalnego
Podobnie jak w poprzednim projekcie, tworzymy archiwum wykonywalne:
![]() |
![]() |
- w [1]: uruchamiamy cel Maven;
- w [2]: występują dwa cele (goals): [clean] służący do usunięcia folderu [target] z projektu Maven oraz [package] służący do jego ponownego wygenerowania;
- w [3]: wygenerowany folder [target] zostanie umieszczony w tym folderze;
- w [4]: generowany jest cel;
W logach wyświetlanych w konsoli ważne jest, aby pojawiła się wtyczka [spring-boot-maven-plugin]. To właśnie ona generuje archiwum wykonywalne.
Za pomocą konsoli przechodzimy do wygenerowanego folderu:
D:\Temp\wksSTS\gs-rest-service\target>dir
...
11/06/2014 15:30 <DIR> classes
11/06/2014 15:30 <DIR> generated-sources
11/06/2014 15:30 11 073 572 gs-rest-service-0.1.0.jar
11/06/2014 15:30 3 690 gs-rest-service-0.1.0.jar.original
11/06/2014 15:30 <DIR> maven-archiver
11/06/2014 15:30 <DIR> maven-status
...
- wiersz 5: wygenerowany plik archiwum;
Archiwum to uruchamia się w następujący sposób:
D:\Temp\wksSTS\gs-rest-service-complete\target>java -jar gs-rest-service-0.1.0.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.1.0.RELEASE)
2014-06-11 15:32:47.088 INFO 4972 --- [ main] hello.Application
: Starting Application on Gportpers3 with PID 4972 (D:\Temp\wk
sSTS\gs-rest-service-complete\target\gs-rest-service-0.1.0.jar started by ST in
D:\Temp\wksSTS\gs-rest-service-complete\target)
...
Uwaga: należy wcześniej zatrzymać usługę internetową, która mogła zostać uruchomiona w Eclipse (patrz punkt 1.6.6).
Teraz, gdy aplikacja internetowa została uruchomiona, można uzyskać do niej dostęp za pomocą przeglądarki:
![]() |
1.7.9. Wdrożenie aplikacji na serwerze Tomcat
Podobnie jak w przypadku poprzedniego projektu, modyfikujemy plik [pom.xml] w następujący sposób:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-rest-service</artifactId>
<version>0.1.0</version>
<packaging>war</packaging>
...
</project>
- wiersz 9: należy wskazać, że zamierzamy wygenerować archiwum WAR (Web ARchive);
Należy również skonfigurować aplikację internetową. W przypadku braku pliku [web.xml] odbywa się to za pomocą klasy dziedziczącej po [SpringBootServletInitializer]:
![]() |
Klasa [ApplicationInitializer] ma następującą postać:
package hello;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
public class ApplicationInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
- wiersz 6: klasa [ApplicationInitializer] rozszerza klasę [SpringBootServletInitializer];
- wiersz 9: metoda [configure] została ponownie zdefiniowana (wiersz 8);
- wiersz 10: podano klasę konfigurującą projekt;
Aby uruchomić projekt, można postępować w następujący sposób:
![]() |
- w [1-2] uruchamia się projekt na jednym z serwerów zarejestrowanych w IDE Eclipse;
Po wykonaniu tej czynności można wywołać URL [http://localhost:8080/gs-rest-service/greeting/?name=Mitchell] w przeglądarce:
![]() |
1.8. Conclusion
Wprowadziliśmy dwa rodzaje projektów Spring MVC:
- projekt, w którym aplikacja internetowa wysyła strumień HTML do przeglądarki. Strumień ten jest generowany przez silnik widoków [Thymeleaf];
- projekt, w którym aplikacja internetowa wysyła strumień jSON do przeglądarki;
W pierwszym przypadku projekt wymaga dwóch zależności Maven:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
W drugim przypadku zależności Maven są następujące:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Konfiguracje te powodują łańcuchowe dodawanie bardzo wielu zależności, z których wiele jest zbędnych. W celu uruchomienia aplikacji zastosujemy ręczną konfigurację Maven, w której znajdą się wyłącznie zależności niezbędne dla projektu.
Wróćmy teraz do podstaw programowania internetowego, przedstawiając dwa podstawowe pojęcia:
- komunikację HTTP (HyperText Transfer Protocol) między przeglądarką a aplikacją internetową;
- język HTML (HyperText Markup Language), który przeglądarka interpretuje w celu wyświetlenia otrzymanej strony;


















































