Darmowy fragment publikacji:
Tytuł oryginału: Mastering Spring MVC 4
Tłumaczenie: Andrzej Watrak
ISBN: 978-83-283-2347-6
Copyright © 2015 Packt Publishing
First published in the English language under the title ‘Mastering Spring MVC 4 – (9781783982387)’.
Polish edition copyright © 2016 by Helion S.A.
All rights reserved.
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording or by any information storage retrieval system,
without permission from the Publisher.
Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej
publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną,
fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje
naruszenie praw autorskich niniejszej publikacji.
Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich
właścicieli.
Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były
kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane
z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie
ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji
zawartych w książce.
Wydawnictwo HELION
ul. Kościuszki 1c, 44-100 GLIWICE
tel. 32 231 22 19, 32 230 98 63
e-mail: helion@helion.pl
WWW: http://helion.pl (księgarnia internetowa, katalog książek)
Pliki z przykładami omawianymi w książce można znaleźć pod adresem:
ftp://ftp.helion.pl/przyklady/smvc4p.zip
Drogi Czytelniku!
Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres
http://helion.pl/user/opinie/smvc4p
Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.
Printed in Poland.
• Kup książkę
• Poleć książkę
• Oceń książkę
• Księgarnia internetowa
• Lubię to! » Nasza społeczność
Spis tre(cid:258)ci
O autorze
O korektorach merytorycznych
Przedmowa
Rozdzia(cid:239) 1. B(cid:239)yskawiczne tworzenie aplikacji Spring
Rozpocz(cid:218)cie pracy w (cid:258)rodowisku Spring Tool Suite
Rozpocz(cid:218)cie pracy w (cid:258)rodowisku IntelliJ
Rozpocz(cid:218)cie pracy w serwisie start.Spring.io
Rozpocz(cid:218)cie pracy za pomoc(cid:200) wiersza polece(cid:241)
Pierwsze kroki
Kompilowanie kodu za pomoc(cid:200) narz(cid:218)dzia Gradle
Chc(cid:218) zobaczy(cid:202) kod!
Spring Boot od wewn(cid:200)trz
Dyspozytor i konfiguracja elementów aplikacji
Interpreter widoków, zasoby statyczne i ustawienia regionalne
Konfiguracja obs(cid:239)ugi b(cid:239)(cid:218)dów i kodowania znaków
Konfiguracja wbudowanego serwletu kontenera serwera (Tomcat)
Port HTTP
Konfiguracja protoko(cid:239)u SSL
Inne opcje konfiguracyjne
Podsumowanie
Rozdzia(cid:239) 2. Tajniki architektury MVC
Architektura MVC
Krytyka architektury MVC i dobre praktyki
Anemiczny model domeny
Informacje ze (cid:283)róde(cid:239)
Platforma MVC 1-0-1
11
12
15
19
20
25
26
26
27
28
32
34
35
38
40
42
44
44
45
46
47
47
48
48
50
50
Poleć książkęKup książkę
Spring MVC 4. Projektowanie zaawansowanych aplikacji WWW
Szablony Thymeleaf
Twoja pierwsza strona
Architektura platformy Spring MVC
Serwlet DispatcherServlet
Przekazywanie danych do widoku
J(cid:218)zyk Spring Expression Language
U(cid:285)ycie parametru przy odczytywaniu danych
Dosy(cid:202) ju(cid:285) „Witaj, (cid:258)wiecie!”, odczytujmy tweety!
Rejestracja aplikacji
Zastosowanie projektu Spring Social
Dost(cid:218)p do serwisu Twitter
Strumienie i funkcje lambda w Java 8
Styl material design i biblioteka WebJars
Uk(cid:239)ady stron
Poruszanie si(cid:218) po witrynie
Punkt kontrolny
Podsumowanie
Rozdzia(cid:239) 3. Obs(cid:239)uga formularzy i z(cid:239)o(cid:285)onych adresów URL
Strona profilu — formularz
Weryfikacja danych
Dostosowanie komunikatów o b(cid:239)(cid:218)dach
Niestandardowe adnotacje do weryfikacji danych
Internacjonalizacja
Zmiana ustawie(cid:241) regionalnych
T(cid:239)umaczenie tekstów aplikacji
Lista w formularzu
Weryfikacja danych po stronie klienta
Punkt kontrolny
Podsumowanie
Rozdzia(cid:239) 4. (cid:146)adowanie plików i obs(cid:239)uga b(cid:239)(cid:218)dów
(cid:146)adowanie plików
Umieszczanie obrazu w odpowiedzi na zapytanie
Zarz(cid:200)dzanie konfiguracj(cid:200) (cid:239)adowania plików
Wy(cid:258)wietlenie za(cid:239)adowanego obrazu
Obs(cid:239)uga b(cid:239)(cid:218)dów (cid:239)adowania plików
T(cid:239)umaczenia komunikatów o b(cid:239)(cid:218)dach
Zapisywanie profilu u(cid:285)ytkownika w sesji
W(cid:239)asne strony z komunikatami o b(cid:239)(cid:218)dach
Zmienne tablicowe w adresach URL
Wszystko razem
Punkt kontrolny
Podsumowanie
4
51
52
54
54
55
56
56
58
58
60
60
62
63
66
67
71
72
73
73
80
82
85
85
87
89
91
94
96
96
99
99
104
104
107
108
112
112
116
117
121
128
129
Poleć książkęKup książkę
Spis tre(cid:286)ci
Rozdzia(cid:239) 5. Tworzenie aplikacji w stylu REST
Czym jest styl REST?
Model dojrza(cid:239)o(cid:258)ci Richardsona
Poziom 0 — HTTP
Poziom 1 — zasoby
Poziom 2 — metody HTTP
Poziom 3 — kontrolki hipermediów
Wersje interfejsu API
Przydatne kody HTTP
Klient jest królem
Diagnostyka interfejsu REST API
Rozszerzenia przegl(cid:200)darek wy(cid:258)wietlaj(cid:200)ce format JSON
Klient REST w przegl(cid:200)darce
Narz(cid:218)dzie httpie
Dostosowanie odpowiedzi JSON
Interfejs API do zarz(cid:200)dzania zasobami u(cid:285)ytkowników
Kody stanu i obs(cid:239)uga wyj(cid:200)tków
Zwrot kodu stanu za pomoc(cid:200) obiektu ResponseEntity
Zwrot kodów stanu za pomoc(cid:200) wyj(cid:200)tków
Dokumentowanie interfejsu za pomoc(cid:200) platformy Swagger
Tworzenie odpowiedzi XML
Punkt kontrolny
Podsumowanie
Rozdzia(cid:239) 6. Zabezpieczanie aplikacji
Podstawowe uwierzytelnienie
Upowa(cid:285)nieni u(cid:285)ytkownicy
Uprawnione adresy URL
Znaczniki bezpiecze(cid:241)stwa w szablonie Thymeleaf
Formularz logowania
Uwierzytelnienie przez Twitter
Konfiguracja uwierzytelnienia spo(cid:239)eczno(cid:258)ciowego
Obja(cid:258)nienia do kodu
Rozproszone sesje
Protokó(cid:239) SSL
Generowanie certyfikatu z w(cid:239)asnym podpisem
Jeden kana(cid:239)
Dwa kana(cid:239)y
Za bezpiecznym serwerem
Punkt kontrolny
Podsumowanie
Rozdzia(cid:239) 7. Zero ryzyka — testy jednostkowe i integracyjne
Dlaczego powinienem testowa(cid:202) swój kod?
Jak powiniene(cid:258) testowa(cid:202) swój kod?
Programowanie zorientowane na testy
131
131
132
132
132
133
134
135
136
137
139
139
139
139
139
144
147
148
149
153
154
156
157
159
159
160
163
164
165
170
170
174
176
178
179
179
180
181
181
182
183
183
184
185
5
Poleć książkęKup książkę
Spring MVC 4. Projektowanie zaawansowanych aplikacji WWW
Testy jednostkowe
Narz(cid:218)dzia odpowiednie do zadania
Testy integracyjne
Twój pierwszy test jednostkowy
Imitacje i atrapy
Imitowanie klas przy u(cid:285)yciu narz(cid:218)dzia Mockito
Tworzenie atrap klas podczas testów
Trzeba u(cid:285)ywa(cid:202) imitacji czy atrap?
Testy jednostkowe kontrolerów REST
Testowanie uwierzytelnienia
Tworzenie testów integracyjnych
Konfiguracja systemu Gradle
Pierwszy test FluentLenium
Obiekty stron w bibliotece FluentLenium
Tworzenie testów w j(cid:218)zyku Groovy
Testy jednostkowe z wykorzystaniem biblioteki Spock
Testy integracyjne z wykorzystaniem biblioteki Geb
Obiekty stron w bibliotece Geb
Punkt kontrolny
Podsumowanie
Rozdzia(cid:239) 8. Optymalizacja zapyta(cid:241)
Produkcyjny profil aplikacji
Kompresja gzip
Kontrola pami(cid:218)ci podr(cid:218)cznej
Pami(cid:218)(cid:202) podr(cid:218)czna aplikacji
Uniewa(cid:285)nianie danych w pami(cid:218)ci podr(cid:218)cznej
Rozproszona pami(cid:218)(cid:202) podr(cid:218)czna
Metody asynchroniczne
Tagi ETag
Protokó(cid:239) WebSocket
Punkt kontrolny
Podsumowanie
Rozdzia(cid:239) 9. Udost(cid:218)pnianie aplikacji w chmurze
Wybór operatora us(cid:239)ug chmurowych
Cloud Foundry
OpenShift
Heroku
Udost(cid:218)pnienie aplikacji w us(cid:239)udze Pivotal Web Services
Instalacja narz(cid:218)dzi konsolowych Cloud Foundry
Z(cid:239)o(cid:285)enie aplikacji
Aktywacja us(cid:239)ugi Redis
Udost(cid:218)pnienie aplikacji w us(cid:239)udze Heroku
Instalacja narz(cid:218)dzi
Konfiguracja aplikacji
Profil Heroku
6
186
187
187
188
191
191
193
195
195
201
202
202
204
209
212
212
215
217
220
221
223
223
224
224
226
231
232
233
237
241
244
244
245
245
246
246
247
247
247
248
252
253
253
254
255
Poleć książkęKup książkę
Spis tre(cid:286)ci
Uruchomienie aplikacji
Aktywacja us(cid:239)ugi Redis
Ulepszanie aplikacji
Podsumowanie
Rozdzia(cid:239) 10. Nie tylko Spring Web
Platforma Spring
Core (rdze(cid:241))
Execution (uruchamianie)
Data (dane)
Inne ciekawe projekty
Wdro(cid:285)enie
Platforma Docker
Aplikacje jednostronicowe
Najwa(cid:285)niejsi gracze
Przysz(cid:239)o(cid:258)(cid:202)
Bezstanowo(cid:258)(cid:202)
Podsumowanie
Skorowidz
256
258
259
260
261
261
262
262
262
263
263
263
264
265
265
266
266
267
7
Poleć książkęKup książkę
Spring MVC 4. Projektowanie zaawansowanych aplikacji WWW
8
Poleć książkęKup książkę4
(cid:146)adowanie plików
i obs(cid:239)uga b(cid:239)(cid:218)dów
W tym rozdziale umo(cid:285)liwisz u(cid:285)ytkownikom (cid:239)adowanie obrazów do profili. Dowiesz si(cid:218) rów-
nie(cid:285), jak obs(cid:239)ugiwa(cid:202) b(cid:239)(cid:218)dy w platformie Spring MVC.
(cid:146)adowanie plików
Teraz umo(cid:285)liwisz u(cid:285)ytkownikom (cid:239)adowanie obrazów do profili. Opis, jak to zrobi(cid:202), znajduje si(cid:218)
w dalszej cz(cid:218)(cid:258)ci rozdzia(cid:239)u, ale teraz upro(cid:258)(cid:202) nieco projekt i utwórz w katalogu templates/profile
now(cid:200) stron(cid:218) uploadPage.html:
!DOCTYPE html
html xmlns:th= http://www.thymeleaf.org
xmlns:layout= http://www.ultraq.net.nz/thymeleaf/layout
layout:decorator= layout/default
head lang= pl
title (cid:146)adowanie obrazu /title
/head
body
div class= row layout:fragment= content
h2 class= indigo-text center (cid:146)adowanie obrazu /h2
form th:action= @{/upload} method= post enctype= multipart/form-data
class= col m8 s12 offset-m2
div class= input-field col s6
input type= file id= file name= file /
/div
div class= col s6 center
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
button class= btn indigo waves-effect waves-light type= submit
(cid:180)name= save
th:text= #{submit} Wy(cid:258)lij
i class= mdi-content-send right /i
/button
/div
/form
/div
/body
/html
Nie ma tu niczego ciekawego poza atrybutem enctype. Plik z obrazem b(cid:218)dzie wysy(cid:239)any metod(cid:200)
POST na adres URL upload. Teraz w pakiecie profile obok klasy ProfileController utwórz od-
powiedni kontroler:
package masterSpringMvc.profile;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Controller
public class PictureUploadController {
public static final Resource PICTURES_DIR = new
(cid:180)FileSystemResource( ./pictures );
@RequestMapping( upload )
public String uploadPage() {
return profile/uploadPage ;
}
@RequestMapping(value = /upload , method = RequestMethod.POST)
public String onUpload(MultipartFile file) throws IOException {
String filename = file.getOriginalFilename();
File tempFile = File.createTempFile( pic , getFileExtension(filename),
PICTURES_DIR.getFile());
try (InputStream in = file.getInputStream();
OutputStream out = new FileOutputStream(tempFile)) {
IOUtils.copy(in, out);
}
100
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
return profile/uploadPage ;
}
private static String getFileExtension(String name) {
return name.substring(name.lastIndexOf( . ));
}
}
Pierwsz(cid:200) operacj(cid:200) wykonywan(cid:200) przez powy(cid:285)szy kod jest utworzenie tymczasowego pliku w kata-
logu pictures (obrazy), znajduj(cid:200)cym si(cid:218) w g(cid:239)ównym katalogu projektu. Sprawd(cid:283) wi(cid:218)c, czy ten
katalog istnieje. W j(cid:218)zyku Java tymczasowy plik posiada unikatowy identyfikator w systemie
plików. Usuni(cid:218)cie pliku zale(cid:285)y od u(cid:285)ytkownika.
Utwórz w g(cid:239)ównym katalogu projektu katalog pictures, a w nim pusty plik o nazwie .gitkeep,
aby katalog by(cid:239) zapisywany w systemie Git.
Puste katalogi w systemie Git
System Git s(cid:239)u(cid:285)y do zarz(cid:200)dzania plikami i nie ma mo(cid:285)liwo(cid:258)ci zapisywania w nim pustych katalogów.
Powszechnie stosowan(cid:200) metod(cid:200) omini(cid:218)cia tego ograniczenia jest stosowanie pustych plików, na przyk(cid:239)ad
.gitkeep. W ten sposób zmusza si(cid:218) system do uwzgl(cid:218)dniania katalogu w procesie kontroli wersji projektu.
Plik (cid:239)adowany przez u(cid:285)ytkownika jest reprezentowany w kontrolerze przez interfejs Multipart
(cid:180)File. Interfejs ten posiada kilka metod zwracaj(cid:200)cych nazw(cid:218) pliku, jego wielko(cid:258)(cid:202) i zawarto(cid:258)(cid:202).
Szczególnie interesuje nas metoda getInputStream. Reprezentowany przez ni(cid:200) strumie(cid:241) wej-
(cid:258)ciowy zostanie skopiowany do metody fileOutputStream za pomoc(cid:200) metody IOUtils.copy.
Pisanie kodu kopiuj(cid:200)cego strumie(cid:241) wej(cid:258)ciowy do wyj(cid:258)ciowego jest do(cid:258)(cid:202) nudne, wi(cid:218)c wygodniej
b(cid:218)dzie skorzysta(cid:202) z biblioteki Apache Utils, zawartej w pliku tomcat-embed-core.jar, umiesz-
czonym w (cid:258)cie(cid:285)ce classpath.
W tej cz(cid:218)(cid:258)ci b(cid:218)dziesz intensywnie wykorzystywa(cid:239) ciekawe funkcjonalno(cid:258)ci platformy Spring
i biblioteki NIO wprowadzonej w wersji Java 7:
(cid:81) pomocnicz(cid:200) klas(cid:218) String reprezentuj(cid:200)c(cid:200) zasób, który mo(cid:285)na przetwarza(cid:202) na ró(cid:285)ne
sposoby;
(cid:81) blok instrukcji try...with automatycznie zamykaj(cid:200)cy strumie(cid:241) nawet w przypadku
wyst(cid:200)pienia wyj(cid:200)tku, dzi(cid:218)ki czemu nie trzeba ka(cid:285)dorazowo definiowa(cid:202) bloku finally.
W powy(cid:285)szym kodzie ka(cid:285)dy plik za(cid:239)adowany przez u(cid:285)ytkownika zostanie skopiowany do ka-
talogu pictures.
Platforma Spring Boot oferuje kilka w(cid:239)a(cid:258)ciwo(cid:258)ci umo(cid:285)liwiaj(cid:200)cych dostosowanie procesu (cid:239)a-
dowania plików. Przyjrzyjmy si(cid:218) klasie MultipartProperties.
101
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
Jej najciekawsze w(cid:239)a(cid:258)ciwo(cid:258)ci to:
(cid:81) multipart.maxFileSize, w(cid:239)a(cid:258)ciwo(cid:258)(cid:202) definiuj(cid:200)ca maksymaln(cid:200) wielko(cid:258)(cid:202) (cid:239)adowanego
pliku. Przy próbie za(cid:239)adowania wi(cid:218)kszego pliku jest zg(cid:239)aszany wyj(cid:200)tek
MultipartException. Domy(cid:258)lna wielko(cid:258)(cid:202) pliku to 1 MB.
(cid:81) multipart.maxRequestSize, w(cid:239)a(cid:258)ciwo(cid:258)(cid:202) definiuj(cid:200)ca maksymaln(cid:200) wielko(cid:258)(cid:202) (cid:285)(cid:200)dania
wielocz(cid:218)(cid:258)ciowego. Domy(cid:258)lna wielko(cid:258)(cid:202) to 10 MB.
Powy(cid:285)sze warto(cid:258)ci domy(cid:258)lne s(cid:200) odpowiednie dla Twojej aplikacji. Po za(cid:239)adowaniu kilku pli-
ków katalog pictures b(cid:218)dzie wygl(cid:200)da(cid:239) nast(cid:218)puj(cid:200)co:
Zaczekaj! Kto(cid:258) za(cid:239)adowa(cid:239) plik ZIP! A(cid:285) trudno w to uwierzy(cid:202). Trzeba w kodzie kontrolera spraw-
dza(cid:202), czy (cid:239)adowane pliki faktycznie reprezentuj(cid:200) obrazy:
package masterSpringMvc.profile;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.io.*;
@Controller
public class PictureUploadController {
public static final Resource PICTURES_DIR = new
FileSystemResource( ./pictures );
@RequestMapping( upload )
public String uploadPage() {
return profile/uploadPage ;
}
@RequestMapping(value = /upload , method = RequestMethod.POST)
public String onUpload(MultipartFile file, RedirectAttributes redirectAttrs)
throws IOException {
if (file.isEmpty() || !isImage(file)) {
redirectAttrs.addFlashAttribute( error ,
Niew(cid:239)a(cid:258)ciwy plik. Za(cid:239)aduj plik z obrazem. );
return redirect:/upload ;
102
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
}
copyFileToPictures(file);
return profile/uploadPage ;
}
private Resource copyFileToPictures(MultipartFile file) throws IOException {
String fileExtension = getFileExtension(file.getOriginalFilename());
File tempFile = File.createTempFile( pic , fileExtension, PICTURES_DIR.getFile());
try (InputStream in = file.getInputStream();
OutputStream out = new FileOutputStream(tempFile)) {
IOUtils.copy(in, out);
}
return new FileSystemResource(tempFile);
}
private boolean isImage(MultipartFile file) {
return file.getContentType().startsWith( image );
}
private static String getFileExtension(String name) {
return name.substring(name.lastIndexOf( . ));
}
}
Bardzo proste! Metoda getContentType zwraca informacj(cid:218) o typie pliku w formacie MIME (ang.
Multipurpose Internet Mail Extensions — uniwersalne rozszerzenie poczty internetowej), np.
image/png, image/jpg itp. Wystarczy wi(cid:218)c sprawdzi(cid:202), czy ci(cid:200)g MIME rozpoczyna si(cid:218) od s(cid:239)owa
image.
W formularzu zosta(cid:239)a zakodowana obs(cid:239)uga b(cid:239)(cid:218)dów, wi(cid:218)c trzeba rozbudowa(cid:202) stron(cid:218) o jakie(cid:258) ele-
menty umo(cid:285)liwiaj(cid:200)ce wy(cid:258)wietlanie komunikatów. Wpisz w pliku uploadPage.html nast(cid:218)puj(cid:200)-
ce wiersze tu(cid:285) poni(cid:285)ej tytu(cid:239)u strony:
div class= col s12 center red-text th:text= ${error} th:if= ${error}
B(cid:239)(cid:200)d (cid:239)adowania pliku
/div
Przy nast(cid:218)pnej próbie za(cid:239)adowania pliku ZIP pojawi si(cid:218) komunikat o b(cid:239)(cid:218)dzie, jak na poni(cid:285)-
szym rysunku:
103
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
Umieszczanie obrazu w odpowiedzi na zapytanie
(cid:146)adowane obrazy nie s(cid:200) zapisywane w statycznych katalogach. Aby wy(cid:258)wietli(cid:202) je na stronie
trzeba przedsi(cid:218)wzi(cid:200)(cid:202) specjalne (cid:258)rodki.
W kodzie strony uploadPage.html tu(cid:285) nad znacznikiem form wpisz poni(cid:285)sze wiersze:
div class= col m8 s12 offset-m2
img th:src= @{/uploadedPicture} width= 100 height= 100 /
/div
Powy(cid:285)szy kod b(cid:218)dzie próbowa(cid:239) pobra(cid:202) obraz z kontrolera. W klasie PictureUploadController
utwórz odpowiedni(cid:200) metod(cid:218):
@RequestMapping(value = /uploadedPicture )
public void getUploadedPicture(HttpServletResponse response) throws IOException {
ClassPathResource classPathResource = new
(cid:180)ClassPathResource( /images/anonymous.png );
response.setHeader( Content-Type ,
URLConnection.guessContentTypeFromName(classPathResource.getFilename()));
IOUtils.copy(classPathResource.getInputStream(), response.getOutputStream());
}
Powy(cid:285)szy kod b(cid:218)dzie umieszcza(cid:239) bezpo(cid:258)rednio w odpowiedzi obraz z pliku src/main/resources/
images/anonymous.png. To bardzo ciekawy sposób!
Je(cid:285)eli ponownie otworzysz stron(cid:218), pojawi si(cid:218) na niej nast(cid:218)puj(cid:200)cy obraz:
Rysunek anonimowego u(cid:285)ytkownika znalaz(cid:239)em na stronie http://iconmonstr.com/user-icon i zapisa(cid:239)em
go jako obraz o wymiarach 128×128 pikseli w pliku PNG.
Zarz(cid:200)dzanie konfiguracj(cid:200) (cid:239)adowania plików
W tym momencie warto jest okre(cid:258)li(cid:202) w pliku konfiguracyjnym application.properties katalog
dla (cid:239)adowanych obrazów i (cid:258)cie(cid:285)k(cid:218) do rysunku anonimowego u(cid:285)ytkownika.
104
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
Utwórz pakiet config, a w nim klas(cid:218) PicturesUploadProperties:
package masterSpringMvc.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import java.io.IOException;
@ConfigurationProperties(prefix = upload.pictures )
public class PictureUploadProperties {
private Resource uploadPath;
private Resource anonymousPicture;
public Resource getAnonymousPicture() {
return anonymousPicture;
}
public void setAnonymousPicture(String anonymousPicture) {
this.anonymousPicture = new DefaultResourceLoader().getResource
(cid:180)(anonymousPicture);
}
public Resource getUploadPath() {
return uploadPath;
}
public void setUploadPath(String uploadPath) {
this.uploadPath = new DefaultResourceLoader().getResource(uploadPath);
}
}
W tej klasie wykorzystany zosta(cid:239) pakiet ConfigurationProperties platformy Spring Boot, umo(cid:285)li-
wiaj(cid:200)cy automatyczne mapowanie w(cid:239)a(cid:258)ciwo(cid:258)ci znalezionych w plikach umieszczonych w (cid:258)cie(cid:285)ce
classpath (domy(cid:258)lnie w pliku application.properties), z uwzgl(cid:218)dnieniem typów tych w(cid:239)a(cid:258)ciwo(cid:258)ci.
Zwró(cid:202) uwag(cid:218), (cid:285)e zdefiniowana jest metoda z argumentem typu String, ale nic nie stoi na prze-
szkodzie, aby inna metoda zwraca(cid:239)a inny typ danych, który b(cid:218)dzie najprzydatniejszy.
Teraz w pakiecie config zdefiniuj klas(cid:218) PicturesUploadProperties:
@SpringBootApplication
@EnableConfigurationProperties({PictureUploadProperties.class})
public class MasterSpringMvc4Application extends WebMvcConfigurerAdapter {
// kod pomini(cid:266)ty
}
W pliku application.properties zdefiniuj nast(cid:218)puj(cid:200)ce w(cid:239)a(cid:258)ciwo(cid:258)ci:
upload.pictures.uploadPath=file:./pictures
upload.pictures.anonymousPicture=classpath:/images/anonymous.png
105
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
Poniewa(cid:285) u(cid:285)yta zosta(cid:239)a klasa DefaultResourceLoader platformy Spring, mo(cid:285)na zastosowa(cid:202) prefiksy,
np. file: lub classpath: do okre(cid:258)lenia po(cid:239)o(cid:285)enia zasobów.
Jest to sposób alternatywny do utworzenia klasy FileSystemResource lub ClassPathResource.
Zalet(cid:200) powy(cid:285)szej metody jest mo(cid:285)liwo(cid:258)(cid:202) dokumentowania kodu. Dzi(cid:218)ki niej od razu wida(cid:202),
(cid:285)e katalog z obrazami znajduje si(cid:218) w g(cid:239)ównym katalogu obiektu, natomiast obraz przedstawiaj(cid:200)cy
anonimowego u(cid:285)ytkownika znajduje si(cid:218) w pliku zapisanym w (cid:258)cie(cid:285)ce classpath.
To wszystko. Teraz mo(cid:285)na wykorzysta(cid:202) zdefiniowane w(cid:239)a(cid:258)ciwo(cid:258)ci w kontrolerze. Poni(cid:285)ej przed-
stawione s(cid:200) odpowiednie cz(cid:218)(cid:258)ci klasy PictureUploadController:
package masterSpringMvc.profile;
import masterSpringMvc.config.PictureUploadProperties;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLConnection;
@Controller
public class PictureUploadController {
private final Resource picturesDir;
private final Resource anonymousPicture;
@Autowired
public PictureUploadController(PictureUploadProperties uploadProperties) {
picturesDir = uploadProperties.getUploadPath();
anonymousPicture = uploadProperties.getAnonymousPicture();
}
@RequestMapping(value = /uploadedPicture )
public void getUploadedPicture(HttpServletResponse response) throws
(cid:180)IOException {
response.setHeader( Content-Type ,
URLConnection.guessContentTypeFromName(anonymousPicture.getFilename()));
IOUtils.copy(anonymousPicture.getInputStream(),
response.getOutputStream());
}
private Resource copyFileToPictures(MultipartFile file) throws IOException {
106
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
String fileExtension = getFileExtension(file.getOriginalFilename());
File tempFile = File.createTempFile( pic , fileExtension,
picturesDir.getFile());
try (InputStream in = file.getInputStream();
OutputStream out = new FileOutputStream(tempFile)) {
IOUtils.copy(in, out);
}
return new FileSystemResource(tempFile);
}
// Pozosta(cid:225)a cz(cid:266)(cid:286)(cid:252) kodu pozostaje bez zmian
}
Je(cid:285)eli uruchomisz aplikacj(cid:218) ponownie, stwierdzisz, (cid:285)e strona si(cid:218) nie zmieni(cid:239)a. Obraz anoni-
mowego u(cid:285)ytkownika b(cid:218)dzie dalej pokazywany, a obrazy (cid:239)adowane przez u(cid:285)ytkowników b(cid:218)d(cid:200)
dalej zapisywane w podkatalogu pictures w g(cid:239)ównym katalogu projektu.
Wy(cid:258)wietlenie za(cid:239)adowanego obrazu
Teraz dobrze by(cid:239)oby wy(cid:258)wietli(cid:202) obraz za(cid:239)adowany przez u(cid:285)ytkownika, prawda? W tym celu
do klasy PictureUploadController dodaj atrybut modelu:
@ModelAttribute( picturePath )
public Resource picturePath() {
return anonymousPicture;
}
Umie(cid:258)(cid:202) powy(cid:285)szy atrybut w kodzie, aby odczyta(cid:202) jego warto(cid:258)(cid:202) po za(cid:239)adowaniu obrazu:
@RequestMapping(value = /uploadedPicture )
public void getUploadedPicture(HttpServletResponse response,
@ModelAttribute( picturePath ) Path picturePath) throws IOException {
response.setHeader( Content-Type ,
URLConnection.guessContentTypeFromName(picturePath.toString()));
Files.copy(picturePath, response.getOutputStream());
}
Adnotacja @ModelAttribute umo(cid:285)liwia wygodne tworzenie atrybutów modelu dla wybranych
metod, które potem mo(cid:285)na umieszcza(cid:202) w metodzie kontrolera z t(cid:200) sam(cid:200) adnotacj(cid:200). W powy(cid:285)szym
kodzie parametr picturePath b(cid:218)dzie dost(cid:218)pny w modelu danych do czasu przekierowania
u(cid:285)ytkownika na inn(cid:200) stron(cid:218). Jego domy(cid:258)ln(cid:200) warto(cid:258)ci(cid:200) jest zdefiniowana we w(cid:239)a(cid:258)ciwo(cid:258)ciach
nazwa pliku z obrazem anonimowego u(cid:285)ytkownika.
Po za(cid:239)adowaniu pliku trzeba zaktualizowa(cid:202) warto(cid:258)(cid:202) atrybutu. Zmie(cid:241) w tym celu metod(cid:218) onUpload:
@RequestMapping(value = /upload , method = RequestMethod.POST)
public String onUpload(MultipartFile file, RedirectAttributes redirectAttrs,
Model model) throws IOException {
if (file.isEmpty() || !isImage(file)) {
107
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
redirectAttrs.addFlashAttribute( error ,
Niew(cid:239)a(cid:258)ciwy plik. Za(cid:239)aduj plik z obrazem. );
return redirect:/upload ;
}
Resource picturePath = copyFileToPictures(file);
model.addAttribute( picturePath , picturePath);
return profile/uploadPage ;
}
Dzi(cid:218)ki umieszczeniu w kodzie odwo(cid:239)ania do modelu mo(cid:285)na odczyta(cid:202) warto(cid:258)(cid:202) parametru picture
(cid:180)Path po za(cid:239)adowaniu obrazu.
Teraz problem polega na tym, (cid:285)e metody onUpload i getUploadedPicture s(cid:200) wywo(cid:239)ywane w ró(cid:285)-
nych zapytaniach. Niestety, warto(cid:258)ci atrybutów modelu s(cid:200) usuwane pomi(cid:218)dzy kolejnymi za-
pytaniami.
Dlatego parametr picturePath trzeba zdefiniowa(cid:202) jako atrybut sesji. W tym celu klas(cid:218) kon-
trolera nale(cid:285)y opatrzy(cid:202) dodatkow(cid:200) adnotacj(cid:200):
@Controller
@SessionAttributes( picturePath )
public class PictureUploadController {
}
Uff! Sporo adnotacji jak na prost(cid:200) obs(cid:239)ug(cid:218) atrybutu sesji. Teraz strona b(cid:218)dzie wygl(cid:200)da(cid:239)a jak
poni(cid:285)ej:
W ten sposób mo(cid:285)na naprawd(cid:218) (cid:239)atwo tworzy(cid:202) kod. Ponadto nie trzeba bezpo(cid:258)rednio stosowa(cid:202)
interfejsu HttpServletRequest ani HttpSession. Co wi(cid:218)cej, mo(cid:285)na równie(cid:285) (cid:239)atwo okre(cid:258)la(cid:202) typ
obiektu.
Obs(cid:239)uga b(cid:239)(cid:218)dów (cid:239)adowania plików
Wnikliwy Czytelnik z pewno(cid:258)ci(cid:200) zauwa(cid:285)y, (cid:285)e powy(cid:285)szy kod mo(cid:285)e zg(cid:239)asza(cid:202) dwa rodzaje wy-
j(cid:200)tków:
108
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
(cid:81) IOException: wyj(cid:200)tek zg(cid:239)aszany w sytuacji, gdy co(cid:258) z(cid:239)ego wydarzy si(cid:218) podczas
zapisywania pliku na dysku;
(cid:81) MultipartException: wyj(cid:200)tek zg(cid:239)aszany w sytuacji, gdy podczas (cid:239)adowania pliku
wyst(cid:200)pi b(cid:239)(cid:200)d, na przyk(cid:239)ad przekroczona zostanie maksymalna wielko(cid:258)(cid:202) pliku.
Teraz jest dobra okazja do zapoznania si(cid:218) z dwoma sposobami obs(cid:239)ugi b(cid:239)(cid:218)dów w (cid:258)rodowisku
Spring:
(cid:81) poprzez lokalne u(cid:285)ycie adnotacji @ExceptionHandler w metodzie kontrolera,
(cid:81) poprzez zdefiniowanie globalnego kontenera serwletów.
Obs(cid:239)u(cid:285) wyj(cid:200)tek IOException za pomoc(cid:200) poni(cid:285)szej metody z adnotacj(cid:200) @ExceptionHandler, utwo-
rzonej w klasie PictureUploadController:
@ExceptionHandler(IOException.class)
public ModelAndView handleIOException(IOException exception) {
ModelAndView modelAndView = new ModelAndView( profile/uploadPage );
modelAndView.addObject( error , exception.getMessage());
return modelAndView;
}
To prosty, ale skuteczny sposób. Powy(cid:285)sza metoda b(cid:218)dzie wywo(cid:239)ywana za ka(cid:285)dym razem, gdy
w kontrolerze zostanie zg(cid:239)oszony wyj(cid:200)tek IOException.
Zg(cid:239)aszanie wyj(cid:200)tków w kodzie Java jest do(cid:258)(cid:202) trudne, wi(cid:218)c w celu sprawdzenia dzia(cid:239)ania me-
tody obs(cid:239)ugi wyj(cid:200)tku zmie(cid:241) na czas testów kod metody onUpload w nast(cid:218)puj(cid:200)cy sposób:
@RequestMapping(value = /upload , method = RequestMethod.POST)
public String onUpload(MultipartFile file, RedirectAttributes
redirectAttrs, Model model) throws IOException {
throw new IOException( Komunikat testowy );
}
Po wprowadzeniu powy(cid:285)szych zmian i za(cid:239)adowaniu obrazu na stronie pojawi si(cid:218) poni(cid:285)szy
komunikat:
109
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
Teraz trzeba obs(cid:239)u(cid:285)y(cid:202) wyj(cid:200)tek MultipartException. Nale(cid:285)y to zrobi(cid:202) na poziomie kontenera
serwletów (czyli serwera Tomcat), poniewa(cid:285) wyj(cid:200)tek ten nie jest zg(cid:239)aszany bezpo(cid:258)rednio przez
kontroler.
W tym celu musisz utworzy(cid:202) now(cid:200) metod(cid:218) konfiguracyjn(cid:200) EmbeddedServletContainerCustomizer
w klasie WebConfiguration:
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer =
new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(MultipartException.class,
(cid:180) /uploadError ));
}
};
return embeddedServletContainerCustomizer;
}
Kod jest nieco rozwlek(cid:239)y. Zwró(cid:202) uwag(cid:218), (cid:285)e EmbeddedServletContainerCustomizer to interfejs
zawieraj(cid:200)cy jedn(cid:200) metod(cid:218), któr(cid:200) mo(cid:285)na zast(cid:200)pi(cid:202) wyra(cid:285)eniem lambda:
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer =
container - container.addErrorPages(new ErrorPage(MultipartException.class,
/uploadError ));
return embeddedServletContainerCustomizer;
}
Mo(cid:285)na wi(cid:218)c po prostu napisa(cid:202) taki kod:
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return container - container.addErrorPages(new
ErrorPage(MultipartException.class,
/uploadError ));
}
Powy(cid:285)szy kod tworzy now(cid:200) stron(cid:218) z komunikatem o b(cid:239)(cid:218)dzie, która b(cid:218)dzie wy(cid:258)wietlana w momen-
cie zg(cid:239)oszenia wyj(cid:200)tku MultipartException. B(cid:218)dzie ona równie(cid:285) powi(cid:200)zana z kodem HTTP.
Interfejs EmbeddedServletContainerCustomizer posiada wiele innych funkcjonalno(cid:258)ci umo(cid:285)li-
wiaj(cid:200)cych dostosowanie kontenera serwletów odpowiednio do dzia(cid:239)ania aplikacji. Wi(cid:218)cej informa-
cji na ten temat jest dost(cid:218)pnych pod adresem http://docs.spring.io/spring-boot/docs/current/ refe-
rence/html/boot-features-developing-web-applications.html#boot-features-customizing-embedded-
containers.
110
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
Teraz w klasie PictureUploadController trzeba obs(cid:239)u(cid:285)y(cid:202) adres URL uploadError:
@RequestMapping( uploadError )
public ModelAndView onUploadError(HttpServletRequest request) {
ModelAndView modelAndView = new ModelAndView( uploadPage );
modelAndView.addObject( error ,
request.getAttribute(WebUtils.ERROR_MESSAGE_ATTRIBUTE));
return modelAndView;
}
Strony z komunikatami o b(cid:239)(cid:218)dach zdefiniowane w kontenerze serwletów posiadaj(cid:200) kilka cie-
kawych atrybutów umo(cid:285)liwiaj(cid:200)cych diagnozowanie b(cid:239)(cid:218)dów:
Atrybut
Opis
javax.servlet.error.status_code
javax.servlet.error.exception_type
javax.servlet.error.message
javax.servlet.error.request_uri
javax.servlet.error.exception
javax.servlet.error.servlet_name
Kod HTTP b(cid:239)(cid:218)du
Klasa wyj(cid:200)tku
Komunikat o wyj(cid:200)tku
Adres URL, dla którego zosta(cid:239) zg(cid:239)oszony wyj(cid:200)tek
W(cid:239)a(cid:258)ciwy wyj(cid:200)tek
Nazwa serwletu zg(cid:239)aszaj(cid:200)cego wyj(cid:200)tek
Z powy(cid:285)szych atrybutów mo(cid:285)na wygodnie korzysta(cid:202) dzi(cid:218)ki klasie WebUtils z biblioteki Spring
Web.
Je(cid:285)eli u(cid:285)ytkownik spróbuje za(cid:239)adowa(cid:202) bardzo du(cid:285)y obraz, pojawi si(cid:218) czytelny komunikat.
Teraz mo(cid:285)esz sprawdzi(cid:202), czy b(cid:239)(cid:200)d jest poprawnie obs(cid:239)ugiwany, (cid:239)aduj(cid:200)c du(cid:285)y plik (wi(cid:218)kszy ni(cid:285)
1 MB) lub ustawiaj(cid:200)c w(cid:239)a(cid:258)ciwo(cid:258)(cid:202) multipart.maxFileSize na mniejsz(cid:200) warto(cid:258)(cid:202), na przyk(cid:239)ad 1 kB.
111
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
T(cid:239)umaczenia komunikatów o b(cid:239)(cid:218)dach
Dla programisty komunikaty o wyj(cid:200)tkach wy(cid:258)wietlane na stronie s(cid:200) naprawd(cid:218) cenne, jednak
dla u(cid:285)ytkownika nie przedstawiaj(cid:200) wi(cid:218)kszej warto(cid:258)ci. Dlatego musisz je przet(cid:239)umaczy(cid:202). W tym
celu w konstruktorze kontrolera nale(cid:285)y zastosowa(cid:202) klas(cid:218) MessageSource:
private final MessageSource messageSource;
@Autowired
public PictureUploadController(PictureUploadProperties uploadProperties,
MessageSource messageSource) {
picturesDir = uploadProperties.getUploadPath();
anonymousPicture = uploadProperties.getAnonymousPicture();
this.messageSource = messageSource;
}
Teraz mo(cid:285)esz odczytywa(cid:202) komunikaty zapisane w pliku:
@ExceptionHandler(IOException.class)
public ModelAndView handleIOException(Locale locale) {
ModelAndView modelAndView = new ModelAndView( profile/uploadPage );
modelAndView.addObject( error ,
messageSource.getMessage( upload.io.exception , null, locale));
return modelAndView;
}
@RequestMapping( uploadError )
public ModelAndView onUploadError(Locale locale) {
ModelAndView modelAndView = new ModelAndView( profile/uploadPage );
modelAndView.addObject( error ,
messageSource.getMessage( upload.file.too.big , null, locale));
return modelAndView;
}
Poni(cid:285)ej zdefiniowane s(cid:200) komunikaty:
upload.io.exception=Podczas (cid:239)adowania pliku wyst(cid:200)pi(cid:239) b(cid:239)(cid:200)d. Spróbuj jeszcze raz.
upload.file.too.big=Plik jest za du(cid:285)y.
I jeszcze komunikaty w j(cid:218)zyku angielskim:
upload.io.exception=An error occurred while uploading the file. Please try again.
upload.file.too.big=Your file is too big.
Zapisywanie profilu u(cid:285)ytkownika w sesji
Kolejn(cid:200) niezb(cid:218)dn(cid:200) rzecz(cid:200) jest zapisywanie profilu u(cid:285)ytkownika w sesji, dzi(cid:218)ki czemu informacje
nie b(cid:218)d(cid:200) z niego usuwane przy ka(cid:285)dorazowym wy(cid:258)wietleniu strony. Takie dzia(cid:239)anie aplikacji
by(cid:239)oby dla u(cid:285)ytkowników irytuj(cid:200)ce, wi(cid:218)c musisz to zmieni(cid:202).
112
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
Sesje HTTP umo(cid:285)liwiaj(cid:200) przechowywanie informacji przesy(cid:239)anych w zapytaniach. Protokó(cid:239) HTTP jest
protoko(cid:239)em bezstanowym, tzn. nie ma mo(cid:285)liwo(cid:258)ci powi(cid:200)zania dwóch zapyta(cid:241) wysy(cid:239)anych przez tego
samego u(cid:285)ytkownika. Wi(cid:218)kszo(cid:258)(cid:202) kontenerów serwletów przypisuje ka(cid:285)demu u(cid:285)ytkownikowi plik cia-
steczka o nazwie JSESSIONID. Plik ten jest przesy(cid:239)any w nag(cid:239)ówku zapytania i umo(cid:285)liwia zapisywanie
dowolnych obiektów w mapie o nazwie HttpSession (sesja HTTP). Taka sesja zazwyczaj ko(cid:241)czy si(cid:218)
w chwili zamkni(cid:218)cia przegl(cid:200)darki przez u(cid:285)ytkownika lub po okre(cid:258)lonym czasie braku jego aktywno(cid:258)ci.
Wcze(cid:258)niej pozna(cid:239)e(cid:258) sposób umieszczania obiektów w sesji za pomoc(cid:200) adnotacji @SessionAttri
(cid:180)butes. Ta metoda dobrze sprawdza si(cid:218) wewn(cid:200)trz jednego kontrolera, ale gdy jest ich kilka,
wtedy pojawiaj(cid:200) si(cid:218) problemy ze wspó(cid:239)dzieleniem danych. Aby móc identyfikowa(cid:202) atrybut na
podstawie jego nazwy, trzeba stosowa(cid:202) ci(cid:200)g znaków, co utrudnia modyfikacj(cid:218) kodu. Z tego
samego powodu nie mo(cid:285)na bezpo(cid:258)rednio manipulowa(cid:202) obiektem HttpSession. Kolejnym ar-
gumentem przemawiaj(cid:200)cym przeciwko bezpo(cid:258)redniemu manipulowaniu sesj(cid:200) s(cid:200) utrudnienia
w wykonywaniu testów jednostkowych kontrolera.
W (cid:258)rodowisku Spring stosuje si(cid:218) jednak inny popularny sposób zapisywania informacji w sesji,
mianowicie opatrywanie metody adnotacj(cid:200) @Scope( session ).
Dzi(cid:218)ki temu mo(cid:285)na umieszcza(cid:202) metody w kontrolerach lub innych komponentach aplikacji
Spring, aby odczytywa(cid:202) b(cid:200)d(cid:283) zapisywa(cid:202) w nich dane.
Utwórz w pakiecie profile klas(cid:218) UserProfileSession:
package masterSpringMvc.profile;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
@Component
@Scope(value = session , proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserProfileSession implements Serializable {
private String twitterHandle;
private String email;
private LocalDate birthDate;
private List String tastes = new ArrayList ();
public void saveForm(ProfileForm profileForm) {
this.twitterHandle = profileForm.getTwitterHandle();
this.email = profileForm.getEmail();
this.birthDate = profileForm.getBirthDate();
this.tastes = profileForm.getTastes();
}
113
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
public ProfileForm toForm() {
ProfileForm profileForm = new ProfileForm();
profileForm.setTwitterHandle(twitterHandle);
profileForm.setEmail(email);
profileForm.setBirthDate(birthDate);
profileForm.setTastes(tastes);
return profileForm;
}
}
Zaimplementowa(cid:239)e(cid:258) wygodny sposób odczytywania i zapisywania danych w postaci obiektu
ProfileForm. Dzi(cid:218)ki temu b(cid:218)dziesz móg(cid:239) zapisywa(cid:202) i odczytywa(cid:202) dane z kontrolera Profile
(cid:180)Controller. Musisz w nim umie(cid:258)ci(cid:202) zmienn(cid:200) typu UserProfileSession i zapisa(cid:202) j(cid:200) jako pole
klasy. Ponadto musisz udost(cid:218)pni(cid:202) obiekt ProfileForm jako atrybut modelu, dzi(cid:218)ki czemu nie
trzeba b(cid:218)dzie stosowa(cid:202) go w metodzie displayProfile. Na koniec dane profilu po ich spraw-
dzeniu nale(cid:285)y zapisa(cid:202):
@Controller
public class ProfileController {
private UserProfileSession userProfileSession;
@Autowired
public ProfileController(UserProfileSession userProfileSession) {
this.userProfileSession = userProfileSession;
}
@ModelAttribute
public ProfileForm getProfileForm() {
return userProfileSession.toForm();
}
@RequestMapping(value = /profile , params = { save }, method =
(cid:180)RequestMethod.POST)
public String saveProfile(@Valid ProfileForm profileForm,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return profile/profilePage ;
}
userProfileSession.saveForm(profileForm);
return redirect:/profile ;
}
// Pozosta(cid:225)a cz(cid:266)(cid:286)(cid:252) kodu bez zmian
}
To wszystko, co trzeba zrobi(cid:202) w aplikacji Spring, aby zapisywa(cid:202) dane w sesji.
Je(cid:285)eli teraz u(cid:285)ytkownik wype(cid:239)ni formularz i od(cid:258)wie(cid:285)y stron(cid:218), wprowadzone dane b(cid:218)d(cid:200) zachowane.
Teraz zamierzam opisa(cid:202) kilka u(cid:285)ytych wcze(cid:258)niej poj(cid:218)(cid:202).
114
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
Pierwsze z nich to umieszczanie (wstrzykiwanie) obiektów w konstruktorze. Konstruktor
kontrolera ProfileController jest opatrzony adnotacj(cid:200) @Autowired, oznaczaj(cid:200)c(cid:200), (cid:285)e platforma
Spring b(cid:218)dzie przetwarza(cid:239)a argumenty konstruktora przed utworzeniem instancji klasy. Alterna-
tywnym rozwi(cid:200)zaniem, nieco mniej obszernym, jest zastosowanie wstrzykiwania pól:
@Controller
public class ProfileController {
@Autowired
private UserProfileSession userProfileSession;
}
Wstrzykiwanie obiektów w konstruktorze jest zdecydowanie lepszym sposobem, poniewa(cid:285)
u(cid:239)atwia wykonywanie testów jednostkowych, gdyby trzeba by(cid:239)o zrezygnowa(cid:202) z testów oferowa-
nych przez platform(cid:218) Spring. Ponadto zale(cid:285)no(cid:258)ci mi(cid:218)dzy klasami s(cid:200) nieco (cid:258)ci(cid:258)lejsze.
Szczegó(cid:239)owy opis wstrzykiwania pól i konstruktorów jest zawarty w doskona(cid:239)ym wpisie na
blogu Olivera Gierkego pod adresem http://olivergierke.de/2013/11/why-field-injection-is-evil.
Kolejn(cid:200) rzecz(cid:200) wymagaj(cid:200)c(cid:200) wyja(cid:258)nienia jest parametr proxyMode (tryb serwera proxy) w adnota-
cji @Scope:
@Scope(value = session , proxyMode = ScopedProxyMode.TARGET_CLASS)
W platformie Spring dost(cid:218)pne s(cid:200) trzy warto(cid:258)ci parametru proxyMode, nie licz(cid:200)c domy(cid:258)lnego:
(cid:81) TARGET_CLASS — oznaczaj(cid:200)cy zastosowanie serwera CGI,
(cid:81) INTERFACES — oznaczaj(cid:200)cy zastosowanie serwera JDK,
(cid:81) NO — oznaczaj(cid:200)cy brak serwera proxy.
Zaleta serwera proxy zazwyczaj ujawnia si(cid:218) podczas wstrzykiwania obiektów do d(cid:239)ugotrwa-
(cid:239)ych komponentów, na przyk(cid:239)ad singletonów. Poniewa(cid:285) wstrzykiwanie odbywa si(cid:218) tylko raz,
podczas tworzenia klasy Bean, wi(cid:218)c w kolejnych wywo(cid:239)aniach tej klasy nie jest uwzgl(cid:218)dniany
jej bie(cid:285)(cid:200)cy stan.
W Twoim przypadku aktualny stan klasy Bean jest zapisywany w sesji, a nie bezpo(cid:258)rednio
w samej klasie. Z tego powodu platforma Spring musi utworzy(cid:202) serwer proxy, który przechwy-
tuje wywo(cid:239)ania metod klasy i wykrywa ich mutacje. Dzi(cid:218)ki temu stan klasy mo(cid:285)e by(cid:202) zapisy-
wany i odczytywany z sesji HTTP.
W przypadku klasy sesji trzeba stosowa(cid:202) tryb proxy. Serwer CGI dokonuje instrumentalizacji
kodu bajtowego i wspó(cid:239)pracuje z ka(cid:285)d(cid:200) klas(cid:200), natomiast serwer JDK jest nieco mniej inge-
rencyjny, ale wymaga zaimplementowania interfejsu.
Ponadto klasa UserProfileSession zosta(cid:239)a zdefiniowana jako implementacja interfejsu Seria
(cid:180)lizable. Nie jest to konieczne, poniewa(cid:285) sesja HTTP mo(cid:285)e przechowywa(cid:202) w pami(cid:218)ci do-
wolne obiekty, ale dobr(cid:200) praktyk(cid:200) jest tworzenie obiektów, które mo(cid:285)na serializowa(cid:202).
115
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
W rzeczywisto(cid:258)ci mo(cid:285)na zmieni(cid:202) sposób przechowywania danych w sesji. W rozdziale 8.,
„Optymalizacja zapyta(cid:241)”, poznasz sposób zapisywania ich w bazie danych Redis, która wspó(cid:239)-
pracuje z obiektami typu Serializable. Najlepiej jest traktowa(cid:202) sesj(cid:218) jako uniwersalny maga-
zyn danych. Zadaniem programisty jest opracowanie sposobu zapisywania i odczytywania da-
nych z tego magazynu.
Aby serializacja klasy przebiega(cid:239)a poprawnie, ka(cid:285)de jej pole równie(cid:285) musi umo(cid:285)liwia(cid:202) seriali-
zacj(cid:218). W naszym przypadku ci(cid:200)gi znaków oraz daty spe(cid:239)niaj(cid:200) ten warunek, wi(cid:218)c nic nie stoi
na przeszkodzie.
W(cid:239)asne strony z komunikatami o b(cid:239)(cid:218)dach
Platforma Spring Boot umo(cid:285)liwia definiowanie w(cid:239)asnych widoków z komunikatami o b(cid:239)(cid:218)dach,
wy(cid:258)wietlanych zamiast opisanej wcze(cid:258)niej strony z komunikatem Whitelabel Error Page. Widok
musi mie(cid:202) nazw(cid:218) error (b(cid:239)(cid:200)d). Jego przeznaczeniem jest obs(cid:239)uga wszystkich wyj(cid:200)tków. Domy(cid:258)lna
klasa BasicErrorController udost(cid:218)pnia wiele przydatnych atrybutów modelu danych, które
mo(cid:285)na wykorzysta(cid:202) na takiej stronie.
Utwórz w katalogu src/main/resources/templates w(cid:239)asn(cid:200) stron(cid:218) error.html do wy(cid:258)wietlania komu-
nikatów o b(cid:239)(cid:218)dach:
!DOCTYPE html
html xmlns:th= http://www.thymeleaf.org
head lang= en
meta charset= UTF-8 /
title th:text= ${status} 404 /title
link href= materialize/css/materialize.css
type= text/css rel= stylesheet
media= screen,projection /
/head
body
div class= row
h1 class= indigo-text center th:text= ${error} Strona nie istnieje /h1
p class= col s12 center th:text= ${message}
(cid:191)(cid:200)dana strona nie istnieje
/p
/div
/body
/html
Je(cid:285)eli teraz u(cid:285)ytkownik otworzy adres URL, który nie b(cid:218)dzie obs(cid:239)ugiwany przez aplikacj(cid:218),
pojawi si(cid:218) w(cid:239)asna strona z komunikatem:
116
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
Bardziej zaawansowanym sposobem obs(cid:239)ugi b(cid:239)(cid:218)dów jest zaimplementowanie klasy ErrorCon
(cid:180)troller, czyli kontrolera do obs(cid:239)ugi wyj(cid:200)tków na poziomie globalnym. Przyjrzyj si(cid:218) klasom
ErrorMvcAutoConfiguration i BasicErrorController, b(cid:218)d(cid:200)cym standardowymi implementa-
cjami tego kontrolera.
Zmienne tablicowe w adresach URL
Teraz wiesz ju(cid:285), jakimi tematami jest zainteresowany u(cid:285)ytkownik. Warto by(cid:239)oby ulepszy(cid:202)
kontroler tak, aby wyszukiwa(cid:239) tweety zawieraj(cid:200)ce s(cid:239)owa kluczowe z zadanej listy.
Jednym z ciekawych sposobów przekazywania w adresie URL par danych klucz-warto(cid:258)(cid:202) jest
zastosowanie zmiennych tablicowych. Mo(cid:285)na to (cid:239)atwo osi(cid:200)gn(cid:200)(cid:202) za pomoc(cid:200) parametrów zapy-
ta(cid:241). Przyjrzyj si(cid:218) poni(cid:285)szemu kodowi:
jaki(cid:258)Url/parametr?zmienna1=warto(cid:258)(cid:202)1 zmienna2=warto(cid:258)(cid:202)2
Zamiast parametru mo(cid:285)na zastosowa(cid:202) tablic(cid:218) zmiennych, jak poni(cid:285)ej:
jaki(cid:258)Url/parametr;zmienna1=warto(cid:258)(cid:202)1;zmienna2=warto(cid:258)(cid:202)2
Ka(cid:285)da warto(cid:258)(cid:202) mo(cid:285)e by(cid:202) równie(cid:285) list(cid:200):
jaki(cid:258)Url/parametr;zmienna1=warto(cid:258)(cid:202)1,warto(cid:258)(cid:202)2;zmienna2=warto(cid:258)(cid:202)3,warto(cid:258)(cid:202)4
Zmienna tablicowa mo(cid:285)e zosta(cid:202) skojarzona w kontrolerze z obiektami ró(cid:285)nych typów, na
przyk(cid:239)ad:
(cid:81) Map String, List ? — typ obs(cid:239)uguj(cid:200)cy wiele zmiennych z wieloma warto(cid:258)ciami;
(cid:81) Map String, ? — typ obs(cid:239)uguj(cid:200)cy zmienne z pojedynczymi warto(cid:258)ciami;
(cid:81) List ? — typ stosowany w przypadku, gdy potrzebna jest jedna zmienna, której
nazw(cid:218) mo(cid:285)na konfigurowa(cid:202).
W Twoim przypadku wymagana jest obs(cid:239)uga adresu jak poni(cid:285)ej:
http://localhost:8080/search/popular;keywords=spring,java
117
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
Pierwszy parametr, popular, oznacza typ wyniku stosowany w interfejsie API do przeszuki-
wania serwisu Twitter. Parametr ten mo(cid:285)e przyjmowa(cid:202) warto(cid:258)ci mixed (mieszane), recent (najnow-
sze) lub popular (popularne).
Pozosta(cid:239)a cz(cid:218)(cid:258)(cid:202) adresu URL zawiera list(cid:218) s(cid:239)ów kluczowych, któr(cid:200) mo(cid:285)na skojarzy(cid:202) z obiektem
typu List String .
Domy(cid:258)lnie platforma Spring MVC usuwa z adresu URL wszystkie znaki za (cid:258)rednikiem. Za-
tem pierwsz(cid:200) rzecz(cid:200), jak(cid:200) trzeba zrobi(cid:202), aby umo(cid:285)liwi(cid:202) zastosowanie zmiennych tablicowych,
jest wy(cid:239)(cid:200)czenie tej funkcjonalno(cid:258)ci.
Wpisz w definicji klasy WebConfiguration poni(cid:285)szy kod:
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
Utwórz w pakiecie search nowy kontroler o nazwie SearchController. Jego zadaniem b(cid:218)dzie
obs(cid:239)uga nast(cid:218)puj(cid:200)cego zapytania:
package masterSpringMvc.search;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.twitter.api.Tweet;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
public class SearchController {
private SearchService searchService;
@Autowired
public SearchController(SearchService searchService) {
this.searchService = searchService;
}
@RequestMapping( /search/{searchType} )
public ModelAndView search(@PathVariable String searchType,
@MatrixVariable List String keywords) {
List Tweet tweets = searchService.search(searchType, keywords);
ModelAndView modelAndView = new ModelAndView( resultPage );
modelAndView.addObject( tweets , tweets);
118
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
modelAndView.addObject( search , String.join( , , keywords));
return modelAndView;
}
}
Jak wida(cid:202), do wy(cid:258)wietlenia tweetów mo(cid:285)na wykorzysta(cid:202) istniej(cid:200)c(cid:200) stron(cid:218) z wynikami wyszu-
kiwania. Ponadto wyszukiwanie b(cid:218)dzie realizowane przez inn(cid:200) klas(cid:218), o nazwie SearchService
(us(cid:239)uga wyszukiwania). Us(cid:239)ug(cid:218) t(cid:218) zdefiniuj w tym samym pakiecie, w którym znajduje si(cid:218) kon-
troler SearchController:
package masterSpringMvc.search;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.twitter.api.Tweet;
import org.springframework.social.twitter.api.Twitter;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SearchService {
private Twitter twitter;
@Autowired
public SearchService(Twitter twitter) {
this.twitter = twitter;
}
public List Tweet search(String searchType, List String keywords) {
return null;
}
}
Teraz musisz zaimplementowa(cid:202) metod(cid:218) search. Argumentem metody twitter.searchOpera
(cid:180)tions().search(parametry) jest obiekt typu searchParameters, umo(cid:285)liwiaj(cid:200)cy zaawansowa-
ne wyszukiwanie tweetów wed(cid:239)ug kilkunastu kryteriów. Ciebie interesuj(cid:200) atrybuty query (za-
pytanie), resultType (typ wyniku) i count (liczba).
Najpierw utwórz konstruktor typu ResultType z parametrem searchType. ResultType jest ty-
pem wyliczeniowym, zatem mo(cid:285)na przegl(cid:200)da(cid:202) ró(cid:285)ne elementy obiektu i wyszukiwa(cid:202) te, które
spe(cid:239)niaj(cid:200) zadane warunki, nie uwzgl(cid:218)dniaj(cid:200)c wielko(cid:258)ci znaków:
private SearchParameters.ResultType getResultType(String searchType) {
for (SearchParameters.ResultType knownType :
SearchParameters.ResultType.values()) {
if (knownType.name().equalsIgnoreCase(searchType)) {
return knownType;
}
}
return SearchParameters.ResultType.RECENT;
}
119
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
Teraz utwórz nast(cid:218)puj(cid:200)c(cid:200) metod(cid:218) typu SearchParameters:
private SearchParameters createSearchParam(String searchType, String taste) {
SearchParameters.ResultType resultType = getResultType(searchType);
SearchParameters searchParameters = new SearchParameters(taste);
searchParameters.resultType(resultType);
searchParameters.count(3);
return searchParameters;
}
Teraz utworzenie konstruktora typu lista obiektów SearchParameters jest proste i polega jedy-
nie na wykonaniu operacji mapowania (pobrania listy s(cid:239)ów kluczowych i zwrócenia dla ka(cid:285)-
dego z nich konstruktora typu SearchParameters):
List SearchParameters searches = keywords.stream()
.map(taste - createSearchParam(searchType, taste))
.collect(Collectors.toList());
Teraz trzeba odczyta(cid:202) tweety z ka(cid:285)dego konstruktora SearchParameters. Mo(cid:285)na to zrobi(cid:202) w nast(cid:218)-
puj(cid:200)cy sposób:
List Tweet tweets = searches.stream()
.map(params - twitter.searchOperations().search(params))
.map(searchResults - searchResults.getTweets())
.collect(Collectors.toList());
Je(cid:285)eli jednak si(cid:218) zastanowisz, odkryjesz, (cid:285)e powy(cid:285)szy kod zwraca list(cid:218) tweetów. Tobie jednak
potrzebna jest prosta, „p(cid:239)aska” lista. Operacj(cid:218) przetwarzania mapy i „sp(cid:239)aszczenia” wyniku reali-
zuje metoda flatMap. Mo(cid:285)na wi(cid:218)c utworzy(cid:202) nast(cid:218)puj(cid:200)cy kod:
List Tweet tweets = searches.stream()
.map(params - twitter.searchOperations().search(params))
.flatMap(searchResults - searchResults.getTweets().stream())
.collect(Collectors.toList());
Parametrem metody flatMap jest strumie(cid:241), co na pocz(cid:200)tku mo(cid:285)e wydawa(cid:202) si(cid:218) niezrozumia(cid:239)e.
Przedstawi(cid:218) najpierw pe(cid:239)ny kod klasy SearchService, aby uzyska(cid:202) ca(cid:239)o(cid:258)ciowy obraz:
package masterSpringMvc.search;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.twitter.api.SearchParameters;
import org.springframework.social.twitter.api.Tweet;
import org.springframework.social.twitter.api.Twitter;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class SearchService {
120
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
private Twitter twitter;
@Autowired
public SearchService(Twitter twitter) {
this.twitter = twitter;
}
public List Tweet search(String searchType, List String keywords) {
List SearchParameters searches = keywords.stream()
.map(taste - createSearchParam(searchType, taste))
.collect(Collectors.toList());
List Tweet results = searches.stream()
.map(params - twitter.searchOperations()
.search(params))
.flatMap(searchResults - searchResults.getTweets()
.stream())
.collect(Collectors.toList());
return results;
}
private SearchParameters.ResultType getResultType(String searchType) {
for (SearchParameters.ResultType knownType :
SearchParameters.ResultType.values()) {
if (knownType.name().equalsIgnoreCase(searchType)) {
return knownType;
}
}
return SearchParameters.ResultType.RECENT;
}
private SearchParameters createSearchParam(String searchType, String taste) {
SearchParameters.ResultType resultType = getResultType(searchType);
SearchParameters searchParameters = new SearchParameters(taste);
searchParameters.resultType(resultType);
searchParameters.count(3);
return searchParameters;
}
}
Teraz, po otwarciu adresu http://localhost:8080/search/mixed;keywords=spring,java, pojawi
si(cid:218) spodziewany efekt, tj. najpierw wyniki wyszukania s(cid:239)owa Spring, a nast(cid:218)pnie Java (patrz
rysunek na nast(cid:218)pnej stronie).
Wszystko razem
Do tej pory poszczególne funkcjonalno(cid:258)ci dzia(cid:239)a(cid:239)y osobno, wi(cid:218)c teraz pora je zebra(cid:202) wszyst-
kie razem. Wykonaj nast(cid:218)puj(cid:200)ce kroki:
121
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
1. Do strony profilu przenie(cid:258) formularz ze strony do (cid:239)adowania obrazów, a nast(cid:218)pnie
usu(cid:241) j(cid:200).
2. Zmie(cid:241) przycisk Wy(cid:258)lij na stronie profilu tak, aby od razu rozpoczyna(cid:239)
wyszukiwanie tweetów wed(cid:239)ug preferencji u(cid:285)ytkownika.
3. Zmie(cid:241) stron(cid:218) startow(cid:200) aplikacji tak, aby od razu pojawia(cid:239)y si(cid:218) na niej wyniki
wyszukiwania spe(cid:239)niaj(cid:200)ce preferencje u(cid:285)ytkownika. Je(cid:285)eli preferencje nie b(cid:218)d(cid:200)
zdefiniowane, powinna pojawi(cid:202) si(cid:218) strona profilu.
Zach(cid:218)cam Ci(cid:218) do samodzielnego przygotowania kodu. By(cid:202) mo(cid:285)e po drodze napotkasz bardzo
powa(cid:285)ne problemy, ale posiadasz ju(cid:285) wystarczaj(cid:200)c(cid:200) wiedz(cid:218), aby je samodzielnie rozwi(cid:200)za(cid:202).
Wierz(cid:218) w Ciebie.
OK. Je(cid:285)eli wykona(cid:239)e(cid:258) zadanie (na pewno tak), rzu(cid:202) okiem na moje rozwi(cid:200)zanie.
Pierwszym krokiem by(cid:239)o usuni(cid:218)cie starej strony uploadPage.html. (cid:165)mia(cid:239)o, zrób to.
Nast(cid:218)pnie tu(cid:285) po elemencie tytu(cid:239)u na stronie profilePage.html wpisa(cid:239)em poni(cid:285)sze wiersze:
div class= row
div class= col m8 s12 offset-m2
img th:src= @{/uploadedPicture} width= 100 height= 100 /
/div
div class= col s12 center red-text th:text= ${error} th:if= ${error}
B(cid:239)(cid:200)d (cid:239)adowania pliku
/div
form th:action= @{/profile} method= post enctype= multipart/form-data
class= col m8 s12 offset-m2
div class= input-field col s6
input type= file id= file name= file /
/div
div class= col s6 center
122
Poleć książkęKup książkęRozdzia(cid:225) 4. • (cid:224)adowanie plików i obs(cid:225)uga b(cid:225)(cid:266)dów
button class= btn indigo waves-effect waves-light type= submit
(cid:180)name= upload
th:text= #{upload} Za(cid:239)aduj
i class= mdi-content-send right /i
/button
/div
/form
/div
Powy(cid:285)szy kod jest bardzo podobny do kodu poprzedniej strony uploadPage.html. Usun(cid:200)(cid:239)em
tylko tytu(cid:239) i zmieni(cid:239)em etykiet(cid:218) na przycisku do wysy(cid:239)ania formularza. W plikach ustawie(cid:241)
regionalnych umie(cid:258)ci(cid:239)em te(cid:285) odpowiednie t(cid:239)umaczenia napisów:
Dla j(cid:218)zyka polskiego:
upload=Za(cid:239)aduj
Dla j(cid:218)zyka angielskiego:
upload=Upload
Zmieni(cid:239)em równie(cid:285) nazw(cid:218) przycisku na upload. Dzi(cid:218)ki temu (cid:239)atwiej b(cid:218)dzie mo(cid:285)na zidentyfi-
kowa(cid:202) operacj(cid:218) (cid:239)adowania pliku po stronie kontrolera.
Teraz przy próbie za(cid:239)adowania obrazu u(cid:285)ytkownik zostanie przekierowany na star(cid:200) stron(cid:218) do
(cid:239)adowania plików. Musia(cid:239)em poprawi(cid:202) kod metody onUpload w klasie PictureUploadController:
@RequestMapping(value = /profile , params = { upload }, method =
(cid:180)RequestMethod.POST)
public String onUpload(@RequestParam MultipartFile file,
RedirectAttributes redirectAttrs) throws IOException {
if (file.isEmpty() || !isImage(file)) {
redirectAttrs.addFlashAttribute( error ,
Niew(cid:239)a(cid:258)ciwy plik. Za(cid:239)aduj plik z obrazem. );
return redirect:/profile ;
}
Resource picturePath = copyFileToPictures(file);
userProfileSession.setPicturePath(picturePath);
return redirect:profile ;
}
Zwró(cid:202) uwag(cid:218), (cid:285)e adres URL obs(cid:239)uguj(cid:200)cy zapytanie POST zosta(cid:239) zmieniony z /upload na /profile.
Obs(cid:239)uga formularza jest o wiele prostsza, je(cid:285)eli zapytania GET i POST dotycz(cid:200) tego samego ad-
resu URL. W szczególno(cid:258)ci oszcz(cid:218)dzaj(cid:200) mnóstwa k(cid:239)opotów przy obs(cid:239)udze wyj(cid:200)tków, ponie-
wa(cid:285) po wyst(cid:200)pieniu b(cid:239)(cid:218)du nie trzeba u(cid:285)ytkownika przekierowywa(cid:202) na inn(cid:200) stron(cid:218).
Usun(cid:200)(cid:239)em równie(cid:285) atrybut picturePath modelu danych. Poniewa(cid:285) jest teraz klasa UserProfile
(cid:180)Session reprezentuj(cid:200)ca sesj(cid:218) u(cid:285)ytkownika, wi(cid:218)c zosta(cid:239)a tutaj wykorzystana. Atrybut picturePath
doda(cid:239)em do klasy UserProfileSession wraz z odpowiednimi metodami przypisuj(cid:200)cymi i od-
czytuj(cid:200)cymi warto(cid:258)ci w(cid:239)a(cid:258)ciwo(cid:258)ci.
123
Poleć książkęKup książkęSpring MVC 4. Projektowanie zaawansowanych aplikacji WWW
Nie mog(cid:239)em zapomnie(cid:202) o zastosowaniu klasy UserProfileSession i udost(cid:218)pnieniu jej jako
pola klasy PictureUploadController.
Pami(cid:218)taj, (cid
Pobierz darmowy fragment (pdf)