3. Akcje: odpowiedź
Rozważmy architekturę aplikacji Spring MVC:
![]() |
W tym rozdziale przyjrzymy się procesowi, w ramach którego żądanie [1] trafia do kontrolera i akcji [2a], które je przetworzą – mechanizm ten nazywamy routingiem. Przedstawiamy również różne odpowiedzi [3], jakie akcja może zwrócić do przeglądarki. Może to być coś innego niż widok V [4b].
3.1. Nowy projekt
Tworzymy nowy projekt Spring MVC:
![]() |
- w [1-2] tworzymy nowy projekt oparty na Spring Boot;
![]() |
- w [3] – nazwa projektu Maven;
- w [4] – grupa Maven, w której zostanie umieszczony wynik kompilacji projektu;
- w [5] – nazwa nadana wynikowi kompilacji;
- w [6] – opis projektu;
- w [7] – pakiet, w którym zostanie umieszczona klasa wykonywalna projektu;
- w [8] – charakter projektu. Jest to projekt internetowy z widokami Thymeleaf. Widoczne są tutaj wszystkie gotowe do użycia zależności Maven oferowane przez projekt Spring Boot;
- w polu [9] określa się, że produkt powstały w wyniku kompilacji Maven zostanie spakowany do archiwum jar, a nie war. Projekt będzie wówczas korzystał z wbudowanego serwera Tomcat, który znajdzie się wśród jego zależności;
- w pliku [10] przechodzimy do kolejnego etapu kreatora;
- w [11] należy wskazać katalog projektu;
![]() |
- w [12] – wygenerowany projekt;
- w [14-15] zmieniamy nazwę pakietu na [istia.st.springmvc];
![]() |
- na [16], nowa nazwa pakietu;
- w [17] – nowy projekt;
Teraz tworzymy nową klasę;
![]() |
- w [1-3] tworzymy nową klasę;
![]() |
- w [5] nadajemy jej nazwę, a w [4] określamy jej pakiet;
- w [6] nowy projekt;
Na razie klasa wygląda następująco:
package istia.st.springmvc;
public class ActionsController {
}
Modyfikujemy ten kod w następujący sposób:
package istia.st.springmvc;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ActionsController {
}
- wiersz 6: adnotacja [@RestController] wskazuje na dwie rzeczy:
- że klasa [ActionsController], opatrzona tą adnotacją, jest kontrolerem Spring MVC, a zatem zawiera akcje przetwarzające URL od klientów;
- że wynik tych akcji jest wysyłany do klienta;
Inna adnotacja [@Controller], z którą się spotkaliśmy, jest inna: akcje kontrolera opatrzonego tą adnotacją zwracają nazwę widoku, który ma zostać wyświetlony. To właśnie połączenie tego widoku z modelem utworzonym przez akcję dla tego widoku stanowi odpowiedź wysyłaną do klienta.
Zmiana struktury naszego projektu pociąga za sobą zmianę jego konfiguracji:
![]() |
Klasa [Application] zmienia się w następujący sposób:
package istia.st.springmvc.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"istia.st.springmvc.controllers"})
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- wiersz 9: adnotacja [ComponentScan] przyjmuje jako parametr tablicę nazw pakietów, w których Spring Boot ma szukać komponentów Spring. W tym przypadku umieszczamy w tej tablicy pakiet [istia.st.springmvc.controllers], aby kontroler oznaczony adnotacją [@RestController] został znaleziony;
Stworzymy różne akcje w kontrolerze, aby zilustrować ich główne cechy. Najpierw skupimy się na różnych typach odpowiedzi, jakie może zwracać akcja w aplikacji bez widoków.
3.2. [/a01, /a02] – Hello world
Nasza pierwsza akcja będzie wyglądała następująco:
@RestController
public class ActionsController {
// ----------------------- hello world ------------------------
@RequestMapping(value = "/a01", method = RequestMethod.GET)
public String a01() {
return "Greetings from Spring Boot!";
}
}
- wiersz 4: adnotacja [RequestMapping] określa żądanie przetwarzane przez akcję z adnotacją:
- atrybut [value] to przetwarzana akcja URL,
- atrybut [method] określa przyjętą metodę;
Zatem metoda [a01] przetwarza żądanie HTTP [GET /a01].
- wiersz 5: metoda [a01] zwraca typ [String], który zostanie wysłany w niezmienionej postaci do klienta;
- wiersz 6: zwracany ciąg znaków;
Uruchommy aplikację tak, jak to już wielokrotnie robiliśmy, a następnie z klientem [Advanced Rest Client], żądamy URL [/a01] z GET [1-2]:
![]() |
- w [3] – odpowiedź serwera;
- na [4], nagłówki odpowiedzi HTTP. Widać, że zastosowanym kodowaniem jest [ISO-8859-1]. Można preferować kodowanie UTF-8. Można to skonfigurować;
- w przypadku [5] wysyłamy to samo zapytanie URL za pomocą przeglądarki Chrome;
Dodajemy następującą akcję [/a02] do kontrolera [ActionsController] (w ten sposób czasami można pomylić URL z metodą, która ją obsługuje pod nazwą akcji):
// ----------------------- znaki z akcentami - UTF8 ------------------------
@RequestMapping(value = "/a02", method = RequestMethod.GET, produces="text/plain;charset=UTF-8")
public String a02() {
return "caractères accentués : éèàôûî";
}
- wiersz 2: atrybut [produces="text/plain;charset=UTF-8"] wskazuje, że akcja wysyła strumień tekstu zawierający znaki zakodowane w formacie [UTF-8]. Format ten umożliwia w szczególności stosowanie znaków akcentowanych;
Aby uwzględnić tę nową akcję, musimy ponownie uruchomić aplikację:
![]() |
Wynik jest następujący:
![]() |
- w formacie [1] widać charakter dokumentu wysłanego przez serwer;
- w [2-3] widoczne są znaki z akcentami;
3.3. [/a03]: przekształcenie strumienia XML
Dodajemy następującą akcję [/a03]:
// ----------------------- text/xml ------------------------
@RequestMapping(value = "/a03", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
public String a03() {
String greeting = "<greetings><greeting>Greetings from Spring Boot!</greeting></greetings>";
return greeting;
}
- wiersz 2: atrybut [produces="text/xml;charset=UTF-8"] wskazuje, że akcja wysyła strumień XML z znakami zakodowanymi w formacie [UTF-8];
Jej wykonanie daje następujący wynik:
![]() |
- w formacie [1] nagłówek HTTP określa, że wysłany dokument jest w formacie HTML;
- w przypadku [2] przeglądarka Chrome wykorzystuje tę informację do sformatowania otrzymanego tekstu XML;
Przypomnijmy, że w przeglądarce Chrome mamy dostęp do wymiany danych HTTP między klientem a serwerem w oknie deweloperskim (Ctrl-Shift-I):

Od tej pory nie będziemy już rutynowo wykonywać zrzutów ekranu z komunikacji HTTP między klientem a serwerem. Czasami wystarczy po prostu podać treść tej komunikacji.
3.4. [/a04, /a05]: wysłanie strumienia jSON
Dodajemy następującą akcję [/a04]:
// ----------------------- generowanie jSON ------------------------
@RequestMapping(value = "/a04", method = RequestMethod.GET)
public Map<String, Object> a04() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("1", "un");
map.put("2", new int[] { 4, 5 });
return map;
}
- wiersz 3: akcja zwraca typ [Map], czyli słownik. Przypomnijmy, że w przypadku kontrolera typu [@RestController] wynikiem akcji jest odpowiedź wysyłana do klienta. Ponieważ protokół HTTP służy do wymiany wierszy tekstu, odpowiedź klienta musi zostać zserializowana do postaci ciągu znaków. W tym celu Spring MVC wykorzystuje różne konwertery [Objet <---> chaîne de caractères]. Powiązanie konkretnego obiektu z konwerterem odbywa się poprzez konfigurację. W tym przypadku funkcja autokonfiguracji Spring Boot sprawdzi zależności projektu:
![]() |
Powyższe zależności Jackson to biblioteki służące do serializacji i deserializacji obiektów na ciągi znaków jSON. Spring Boot wykorzysta następnie te biblioteki do serializacji i deserializacji obiektów zwracanych przez akcje. Przykład kodu Java służącego do serializacji i deserializacji obiektów Java w formacie jSON znajduje się w rozdziale 9.7.
Warto zauważyć, że w linii 2 nie podaliśmy typu wysyłanej odpowiedzi. Sprawdzimy, jaki typ zostanie wysłany domyślnie.
W przeglądarce Chrome wyniki są następujące: [1-3]:
![]() |
Dodajmy teraz następującą akcję: [/a05]:
// ----------------------- generowanie jSON - 2 ------------------------
@RequestMapping(value = "/a05", method = RequestMethod.GET)
public Personne a05() {
return new Personne(1,"carole",45);
}
Klasa [Personne] wygląda następująco:
![]() |
package istia.st.sprinmvc.models;
public class Personne {
// identyfikator
private Integer id;
// nazwisko
private String nom;
// wiek
private int age;
// konstruktorzy
public Personne() {
}
public Personne(String nom, int age) {
this.nom = nom;
this.age = age;
}
public Personne(Integer id, String nom, int age) {
this(nom, age);
this.id = id;
}
@Override
public String toString() {
return String.format("[id=%s, nom=%s, age=%d]", id, nom, age);
}
// metody pobierające i ustawiające
...
}
Wyniki wykonania są następujące:
![]() |
- w przypadku [1] serwer wskazuje, że wysyłany dokument to jSON;
- w przypadku [2] – otrzymano dokument jSON;
3.5. [/a06]: zwracanie pustego strumienia
Dodajemy następującą akcję [/a06]:
// ----------------------- zwracanie pustego strumienia ------------------------
@RequestMapping(value = "/a06")
public void a06() {
}
- w wierszu 3 akcja [/a06] nie zwraca żadnej wartości. Spring MVC wygeneruje wówczas pustą odpowiedź dla klienta;
Wykonanie daje następujące wyniki:
![]() |
Powyżej atrybut HTTP [Content-Length] w odpowiedzi wskazuje, że serwer wysyła pusty dokument.
3.6. [/a07, /a08, /a09]: rodzaj strumienia z [Content-Type]
Dodajemy następującą akcję [/a07]:
// ----------------------- text/html ------------------------
@RequestMapping(value = "/a07", method = RequestMethod.GET, produces = "text/html;charset=UTF-8")
public String a07() {
String greeting = "<h1>Greetings from Spring Boot!</h1>";
return greeting;
}
- w wierszu 2 akcja [/a07] generuje strumień HTML [text/html];
- wiersz 4: ciąg znaków HTML;
Wykonanie daje następujące wyniki:
![]() |
- w [1] widać, że Chrome zinterpretował tag HTML <h1>, który wyświetla jego zawartość dużymi literami;
Teraz zróbmy to samo z następującą akcją [/a08]:
// ----------------------- wynik HTML w formacie text/plain ------------------------
@RequestMapping(value = "/a08", method = RequestMethod.GET, produces = "text/plain;charset=UTF-8")
public String a08() {
String greeting = "<h1>Greetings from Spring Boot!</h1>";
return greeting;
}
- wiersz 2: odpowiedź akcji ma typ [text/plain];
Wyniki są następujące:
![]() |
- w przypadku [1] przeglądarka Chrome nie zinterpretowała tagu HTML <h1>, ponieważ serwer poinformował ją, że wysyła strumień [text/plain] [2];
Spróbujmy ponownie wykonać podobną operację z następującą akcją [/a09]:
// ----------------------- wynik HTML w formacie text/xml ------------------------
@RequestMapping(value = "/a09", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
public String a09() {
String greeting = "<h1>Greetings from Spring Boot!</h1>";
return greeting;
}
- wiersz 2: wysyłamy strumień typu [text/xml];
Wyniki są następujące:
![]() |
- w przypadku [1] przeglądarka Chrome nie zinterpretowała tagu HTML <h1>, ponieważ serwer poinformował ją, że wysyła strumień [text/xml] [2]. W związku z tym potraktował tag <h1> jako tag XML;
Z tych przykładów wynika, jak ważny jest nagłówek HTTP [Content-Type] w odpowiedzi serwera. Przeglądarka wykorzystuje ten nagłówek, aby wiedzieć, jak zinterpretować otrzymany dokument;
3.7. [/a10, /a11, /a12]: przekierowanie klienta
Tworzymy nowy kontroler [RedirectController]:
![]() |
Kod dla [RedirectCntroller] będzie na razie wyglądał następująco:
package istia.st.springmvc.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class RedirectController {
}
- wiersz 7: używamy adnotacji [@Controller], co sprawia, że od tej pory domyślnie typ [String] wyniku akcji oznacza nazwę akcji lub widoku;
Tworzymy następującą akcję [/a10]:
// ------------ przekierowanie do akcji strony trzeciej -----------------------
@RequestMapping(value = "/a10", method = RequestMethod.GET)
public String a10() {
return "a01";
}
- wiersz 4: jako wynik zwracamy „a01”, czyli nazwę akcji. To właśnie ona wyśle odpowiedź do klienta;
Oto przykład:
![]() |
- w akcji [2] otrzymaliśmy strumień danych z akcji [/a01];
- w przypadku [3] przeglądarka wyświetla URL akcji [/a10];
Teraz tworzymy następującą akcję o nazwie [/a11]:
// ------------ tymczasowe przekierowanie 302 do akcji strony trzeciej -----------------------
@RequestMapping(value = "/a11", method = RequestMethod.GET)
public String a11() {
return "redirect:/a01";
}
Otrzymujemy następujące wyniki:
![]() |
- w logach przeglądarki Chrome [1-2] widoczne są dwa żądania: jedno skierowane do [/a11], drugie do [/a01];
- w przypadku [3] serwer odpowiada kodem [302], który nakazuje przeglądarce klienckiej przekierowanie się doURL wskazany w nagłówku HTTP [Location:] [4]. Kod [302] jest kodem przekierowania tymczasowego;
Następnie przeglądarka wysyła drugie żądanie do serwera przekierowującego o numerze URL:
![]() |
- do adresu [5], co stanowi drugie żądanie klienta;
- do [6], przeglądarka klienta wyświetla URL z żądania przekierowania;
Można chcieć wskazać przekierowanie stałe, w którym to przypadku należy wysłać do klienta następujący nagłówek HTTP:
co oznacza, że przekierowanie jest stałe. Ta różnica między przekierowaniem tymczasowym (302) a stałym (301) jest uwzględniana przez niektóre wyszukiwarki.
Piszemy akcję [/a12], która spowoduje to stałe przekierowanie:
// ------------ stałe przekierowanie 301 do akcji strony trzeciej----------------
@RequestMapping(value = "/a12", method = RequestMethod.GET)
public void a12(HttpServletResponse response) {
response.setStatus(301);
response.addHeader("Location", "/a01");
}
- wiersz 3: prosimy Spring o wstrzyknięcie obiektu MVC, który zawiera odpowiedź wysyłaną do klienta;
- wiersz 4: ustalamy [status] odpowiedzi, [301] nagłówka oraz HTTP:
- wiersz 5: ręcznie tworzymy następujący nagłówek HTTP:
który jest nagłówkiem przekierowującym URL.
Wynikiem wykonania są następujące dane:
![]() | ![]() |
Z tego przykładu warto zapamiętać, w jaki sposób:
- wygenerować status odpowiedzi HTTP;
- dołączyć nagłówek HTTP do odpowiedzi;
3.8. [/a13]: wygenerować pełną odpowiedź
Możliwe jest pełne kontrolowanie odpowiedzi, co pokazuje poniższa akcja klasy [ResponsesController]:
![]() |
// ----------------------- pełne wygenerowanie odpowiedzi ------------------------
@RequestMapping(value = "/a13")
public void a13(HttpServletResponse response) throws IOException {
response.setStatus(666);
response.addHeader("header1", "qq chose");
response.addHeader("Content-Type", "text/html;charset=UTF-8");
String greeting = "<h1>Greetings from Spring Boot!</h1>";
response.getWriter().write(greeting);
}
- wiersz 3: wynikiem akcji jest [void]. W tym przypadku, aby wysłać do klienta odpowiedź niepustą, należy użyć obiektu [HttpServletResponse response] dostarczonego przez Spring MVC;
- wiersz 4: nadajemy odpowiedzi status, który nie zostanie rozpoznany przez klienta;
- wiersz 5: dodajemy nagłówek HTTP, który nie zostanie rozpoznany przez klienta;
- wiersz 6: dodajemy nagłówek HTTP [Content-Type] w celu określenia typu strumienia, który zostanie wysłany, w tym przypadku HTML;
- wiersze 7–8: dokument, który będzie następował po nagłówkach HTTP w odpowiedzi;
Wyniki są następujące:
![]() |
- w [1] rozpoznajemy elementy naszej odpowiedzi;
- w [2-3] widać, że Chrome zignorował fakt, iż:
- status odpowiedzi HTTP nie był rozpoznanym statusem HTTP,
- nagłówek [header1] nie był rozpoznawalnym nagłówkiem HTTP;
Jeśli klientem nie jest przeglądarka, lecz klient programowy, można dowolnie używać dowolnych statusów i nagłówków.



























