Komunikacja frontend www z backend w Javie
W tym artykule postaram się wyjaśnić jak wygląda komunikacja warstwy prezentacji, np. strony www z warstwą logiki biznesowej, np. service CRUD w języku Java. Komunikacja za pomocą protokołu HTTP i metod tego protokołu (GET i POST). Przedstawię i wyjaśnię pojęcia: klient-serwer, żądanie, odpowiedź, EL (Expression Language).
Jacek Jabłonka. Mentor, Trener – od zera do Junior Java Developer’a. Twórca i autor bloga www.juniorjavadeveloper.pl – poradnika dla przyszłych Junior Java Developer’ów. Doświadczenie umożliwia mu pomoc innym w przekwalifikowaniu się, zmianie zawodu na Junior Java Developer’a. Przez 12 lat pracował jako konsultant informatyczny, głównie w języku Java. Od września 2018 roku zajmuje się przebranżawianiem osób na Junior Java Developer’a.
Będę korzystał ze Spring MVC i Thymeleaf, ale gruntowna wiedza nie jest wymagana dla tych technologii. Na temat warstw aplikacji pisałem w artykule Aplikacje Java mają warstwy jak tort urodzinowy – aplikacja trójwarstwowa, a na temat klasy jako service CRUD pisałem w artykule Pierwsza klasa jako serwis CRUD – kod Java, IntelliJ, krok po kroku.
Przedstawione elementy:
- Podstawy obsługi żądań protokołu HTTP – bez framework, czysty HTML.
- Komunikacja klient strona www – front-end – z serwerem aplikacja Java – back-end.
- Omówienie pojęć związanych z komunikacją klient-serwer – HTTP, Request, URL itp.
- Implementacja aplikacji w Spring MVC do komunikacji klient-serwer, back-end i front-end.
- Narzędzia do weryfikacji komunikacji front-end z back-end.
W przykładzie stworzę dynamiczną stronę www, dla której wykorzystam formularz, umożliwiający utworzenie notatki oraz wyświetlenie listy utworzonych notatek. Przykład jest bardzo prosty, ale w zupełności wystarczy do zaprezentowania komunikacji klient-serwer dla front-end i back-end.
<html> <head> </head> <body> <h1>Create note</h1> <form action="index.html" method="get"> <input type="text" name="title"> <input type="text" name="content"> <button type="submit">Save</button> </form> </body> </html>
Powyższy kod przedstawia formularz www umożliwiający pobranie i przesłanie wpisanych danych – czysty kod HTML.
Powyższy kod HTML pokazuje jak niewiele wystarczy, żeby wysłać dane ze strony www, oczywiście w tym momencie dane nigdzie nie trafiają, są tracone. Wysłanie wpisanych przez użytkownika danych następuje po wciśnięciu przycisku Save. Żeby wysłane dane zostały odebrane i przetworzone przez aplikację potrzebujemy po pierwsze serwera www np. Apache Tomcat, a po drugie nasza aplikacja musi posiadać implementację, kod np. Spring MVC odczytujący dane wysłane z przeglądarki.
Należy zwrócić uwagę na adres URL strony i parametry żądania: java-frontend-backend/index.html?title=Note+title&content=Note+content.
Dodatkowo w tym przypadku nie był potrzebny serwer www, plik index.html
był po prostu zapisany lokalnie na dysku, a do jego wyświetlenia użyłem przeglądarki internetowej. W tej sytuacji mówimy o statycznej stronie, która jest wyświetlana tak jak została zakodowana w pliku HTML. Wykorzystanie Javascript do dodania dynamicznych treści, to nie to samo (wyjątek stanowi Node.js), co połączenie Java back-end z www front-end. W dalszej części pokażę jak zamienić statyczną stronę na dynamiczną z użyciem wspomnianego back-end napisanego w języku Java.
Żeby korzystać z dynamicznych stron www potrzebujemy back-end, który będzie przyjmował żądania i wysyłał odpowiedzi użytkownika/klienta. W tym momencie zaczynamy działać w konfiguracji klient-serwer (client-server). Kod back-end’u musi być umieszczony na serwerze www np. Apache Tomcat. Serwer www na czas development’u będzie zainstalowany lokalnie na komputerze programisty, dodatkowo, jeżeli chcemy udostępnić naszą aplikację innym użytkownikom internetu potrzebujemy zewnętrznego serwera np. Heroku.com. Przyjęło się, że lokalne serwery www mają adres http://localhost:8080, gdzie http to protokół, localhost to nazwa serwera, 8080 to port serwera. Aplikację z back-end należy umieścić na serwerze, czyli wykonać deploy na serwer.
Szablon aplikacji zawierającej back-end w Java z wykorzystaniem Spring Framework oraz Spring Boot generujemy na stronie start.spring.io. Poniżej zrzut ekranu prezentujący pola, które należy wypełnić oraz zależności „Dependencies”, które należy wybrać.
Project - Maven Project Language - Java Spring Boot - 2.4.3 Project Metadata: Group - np. pl.nazwisko.imie.java.medicalcenter (odwrotna nazwa domenowa), Artifact - np. medical-center (nazwa aplikacji, pliku jar), Name - nazwa aplikacji, Description - opis aplikacji, Package name - np. pl.nazwisko.imie.java.medicalcenter (pakiety w projekcie), Packaging - Jar, Java - 11. Dependencies: Spring Web - moduł Spring dla aplikacji webowych.
Teraz statyczną stronę index.html zamienimy na dynamiczną, która będzie dostępna pod adresem http://localhost:808/notes
w odróżnieniu od poprzedniej wersji, która była po prostu wskazaniem pliku na dysku file:///home/user/dev/juniorjavadeveloper.pl/java-frontend-backend/index.html
. Korzystając z metod (GET, POST) protokołu HTTP oraz stosując REST dla tego samego adresu URL będziemy mogli wysłać na serwer dane formularza oraz pobrać dane z serwera.
Dane z front-end do back-end wysyłane są w postaci tekstu:
- dla metody GET dane w postaci nazwa/wartość (klucz/wartość) np. title=Note
- dla metody POST dane w postaci JSON np. {„title”:”Note”}
Moduł Spring MVC ułatwia nam pracę przy tworzeniu aplikacji web, nie musimy już tworzyć dużej ilości kodu jak w przypadku Java Servlets. Wykorzystamy wzorzec projektowy MVC (Model View Controller), który jest domyślnie wspierany przez Spring, nawet sam moduł dla aplikacji web’owych w Spring nosi nazwę Spring MVC.
Zaczniemy od stworzenia poszczególnych elementów MVC, w naszym przypadku:
- Model – klasa przechowująca dane wprowadzone w formularzu przez użytkownika,
- View – dynamiczna strona HTML pobierająca i wyświetlająca dane,
- Controller – klasa obsługująca różne żądania protokołu HTTP (np. GET, POST).
Kolejność tworzenia wyżej wymienionych elementów nie ma znaczenia, ale ja przyjąłem, że najpierw tworzę Model, później Controller, a na końcu View. Poniżej zaprezentuję kod poszczególnych elementów oraz zrzuty ekranu z aplikacji.
public class NoteModel { private String title; private String content; public NoteModel(String title, String content) { this.title = title; this.content = content; } // getters, setters // toString() } Powyższy kod Java reprezentujący Model danych notatki w aplikacji. @Controller @RequestMapping(value = "/notes") public class NoteController { public static final Logger LOGGER = Logger.getLogger(NoteController.class.getName()); private List<NoteModel> notes = new ArrayList<>(); @GetMapping public String list() { LOGGER.info("list() = " + notes); return "notes/notes"; } @GetMapping(value = "/create") public String createView() { return "notes/create-note"; } @PostMapping public String create(NoteModel note) { LOGGER.info("create(" + note + ")"); notes.add(note); return "redirect:/notes"; } }
Powyższy kod Java reprezentuje Controller do obsługi żądań protokołu HTTP.
W aplikacji web operacje związane z formularzem, czyli przesłaniem danych z front-end do back-end można podzielić na trzy części:
- Żądanie, wyświetlenie strony z formularzem – metoda
createView()
. - Walidacja, sprawdzenie poprawności danych – @Valid + @NotBlank.
- Submit, przesłanie danych do back-end – metoda
create()
.
Powyżej opisane metody createView()
i create()
odnoszą się bezpośrednio do metod CRUD, w tym przypadku C – create, metoda createView()
odnosi się do widoku umożliwiającego wyświetlenie formularza. Analogicznie dla całego CRUD:
- C – create, metody:
createView()
icreate()
, - R – read, metody:
readView()
iread()
, - U – update, metody:
updateView()
iupdate()
, - D – delete, metody:
deleteView()
idelete()
.
Ad. 1. Żądanie, wyświetlenie strony z formularzem – metoda createView()
.
<a href="/notes/create">Add Note</a>
Powyższy kod przedstawia link „Add Note” umożliwiający wyświetlenie strony z formularzem www.
Powyżej jeden ze sposobów na wyświetlenie strony z formularzem, w tym przypadku zrealizowane za pomocą linku, który kieruje na odpowiednią stronę. Ta część realizowana jest przez front-end.
@GetMapping(value = "/create") public String createView() { return "notes/create-note"; }
Powyższy kod odpowiada za obsługę żądań protokołu HTTP z wykorzystaniem Spring MVC oraz szablonów stron HTML z wykorzystaniem Thymeleaf. W telegraficznym skrócie, jeżeli chcemy dodać komunikację front-end z back-end dla www musimy obsługiwać żądania protokołu HTTP.
- @GetMapping(value = „/create”) – obsługa metody GET, dla URI /create, cały adres będzie wyglądał
http://localhost:8080/create
– Spring MVC. - return „notes/create-note”; – wynikiem metody create() będzie strona HTML create-note zawierająca formularz www – Thymeleaf.
Ad. 2. Walidacja, sprawdzenie poprawności danych – @Valid + @NotBlank.
<form action="#" th_action="@{/notes}" th_object="${note}" method="post"> <input type="text" th_field="${note.title}"> <p th_if="${#fields.hasErrors('title')}" th_errors="*{title}"></p> <input type="text" th_field="${note.content}"> <p th_if="${#fields.hasErrors('content')}" th_errors="*{content}"></p> <button type="submit">Save</button> </form>
Powyższy kod przedstawia formularz HTML z elementami odpowiedzialnymi za wyświetlanie wyników walidacji w Thymeleaf – kod HTML.
@PostMapping public String create(@Valid @ModelAttribute(name = "note") NoteModel note, BindingResult bindingResult) { LOGGER.info("create(" + note + ")"); if (bindingResult.hasErrors()) { LOGGER.info("validation errors in Model: " + note); LOGGER.info("validation errors: " + bindingResult.getAllErrors()); return "notes/create-note"; } notes.add(note); return "redirect:/notes"; }
Powyższy kod przedstawia obsługę żądania HTML POST wraz z walidacją – @Valid
– przesyłanych danych w Spring MVC – kod Java.
public class NoteModel { @NotBlank(message = "Title must not be blank") private String title; @NotBlank(message = "Content must not be blank") private String content; }
Powyższy kod przedstawia Model z regułami walidacji – @NotBlank
– po stronie back-end w Spring MVC – kod Java.
Ad. 3. Submit, przesłanie danych do back-end – metoda create()
.
<form action="/notes" method="post"> [...] <button type="submit">Save</button>
Powyższy kod przedstawia fragmenty formularza HTML odpowiedzialne za przesłanie żądania HTTP POST z front-end do back-end – kod HTML.
@PostMapping public String create(@Valid NoteModel note) { LOGGER.info("create(" + note + ")"); notes.add(note); return "redirect:/notes"; }
Powyższy kod przedstawia obsługę żądania HTML POST wraz z walidacją – @Valid – przesyłanych danych w Spring MVC – kod Java.
Tworząc aplikację web’ową z dynamicznymi stronami www trzeba się przyzwyczaić do faktu, że kod HTML programisty dla tworzonych stron www będzie różnił się od tego, co zobaczy użytkownik końcowy wyświetlający stronę www. Dlaczego tak się dzieje opisałem poniżej w artykule.
Trzy różne wersje kodu HTML dla tej samej strony www:
- HTML bez Thymeleaf – statyczna strona www,
- HTML z Thymeleaf – dynamiczna strona www,
- HTML wygenerowany – przetworzona, sparsowana strona www.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Create Note</title> </head> <body> <h1>Create note</h1> <form action="/notes" method="post"> <input type="text" name="title"> <input type="text" name="content"> <button type="submit">Save</button> </form> </body> </html>
Powyższy kod przedstawia HTML bez Thymeleaf – kod HTML.
<!DOCTYPE html> <html lang="en" xmlns_th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Create Note</title> </head> <body> <h1>Create note</h1> <form action="#" th_action="@{/notes}" th_object="${note}" method="post"> <input type="text" th_field="${note.title}"> <input type="text" th_field="${note.content}"> <button type="submit">Save</button> </form> </body> </html>
Powyższy kod przedstawia HTML z Thymeleaf – kod HTML.
<html lang="en"> <head> <meta charset="UTF-8"> <title>Create Note</title> </head> <body> <h1>Create note</h1> <form action="/notes" method="post"> <input type="text" id="title" name="title" value=""> <input type="text" id="content" name="content" value=""> <button type="submit">Save</button> </form> </body> </html>
Powyższy kod przedstawia wygenerowany HTML – kod HTML.
W kodzie HTML wygenerowanym przez back-end i przesłanym do front-end nie ma żadnych elementów Thymeleaf. Dzieje się tak dlatego, że back-end przetwarza, parsuje stronę zawierającą HTML i podmienia wystąpienia elementów Thymeleaf np.:
Thymeleaf: <input type="text" th_field="${note.title}"> na HTML: <input type="text" id="title" name="title" value="">
Tak działa większość silników szablonów html (template engine) wspieranych przez framework do tworzenia aplikacji www. Silnik szablonów i framework, to dwie oddzielne rzeczy, np. Spring MVC wspiera więcej niż jeden silnik szablonów, Thymeleaf jest jednym z nich.
Spring MVC w połączeniu z Thymeleaf wykorzystuje EL (Expression Language), EL pozwala połączyć dane przesyłane z front-end HTML do back-end w Java. Dane z front-end do back-end wysyłane są w postaci tekstu np.:
- dla metody GET dane w postaci nazwa/wartość (klucz/wartość) np. title=Note – request/query parameters (parametry żądania),
- dla metody POST dane w postaci JSON np. {„title”:”Note”} – payload/request body (ciało żądania).
Spring MVC parsuje parametry żądania (request/query parameters) metody GET lub ciało żądania (payload/request body) metody POST i zamienia na obiekty klas języka Java.
<form action="#" th_action="@{/notes}" th_object="${note}" method="post"> <input type="text" th_field="${note.title}"> <input type="text" th_field="${note.content}"> <button type="submit">Save</button> </form>
Powyższe pola <input>
zostaną zamienione na parametry żądania po stronie front-end, a następnie zostaną przesłane w postaci tekstu do back-end.
@PostMapping public String create(@Valid @ModelAttribute(name = "note") NoteModel note, BindingResult bindingResult) { //... }
Spring MVC zamieni parametry żądania z front-end na obiekt note klasy NoteModel po stronie back-end.
Poniżej zaprezentuję niezbędne narzędzia do weryfikacji komunikacji front-end z back-end bez, których moim zdaniem praca na linii front-end z back-end jest niemożliwa.
- Aplikacja Postman – weryfikacja komunikacji front-end z back-end, żądania protokołu HTTP,
- Web Developer Tools – wbudowane w każdą przeglądarkę internetową (F12 włącza) – weryfikacja strony www w kwestii kodu HTML, Javascript oraz strony wizualnej, jak również żądania protokołu HTTP,
- Logi aplikacji back-end – informacje o tym, co dzieje się po stronie back-end.
Całość kodu można znaleźć na: https://github.com/juniorjavadeveloper-pl/java-frontend-backend.git.
Wyjaśnienie pojęć (w telegraficznym skrócie):
- Protokół HTTP (ang. Hypertext Transfer Protocol) – protokół przesyłania dokumentów hipertekstowych, to protokół sieci WWW (ang. World Wide Web). Hipertekstowy – sposób organizacji informacji w tekście komputerowym, polegający na zastosowaniu wyróżnionych odsyłaczy, które automatycznie przenoszą użytkownika do innych informacji. W skrócie, to strony www z linkami do innych elementów/stron www.
- URL (ang. Uniform Resource Locator) – oznacza ujednolicony format adresowania (określania lokalizacji) zasobów (informacji, danych, usług) stosowany w Internecie i w sieciach lokalnych. Tak zwany adres URL najczęściej kojarzony jest z adresami stron WWW, ale ten format adresowania służy do określania lokalizacji wszelkich zasobów dostępnych w Internecie.
- Parametry żądania (ang. request/query parameters) – dokładnie query string, dodawane są do adresu URL, stanową jego część, w której parametrom przypisywane są wartości. W tym przykładzie mają postać
java-frontend-backend/index.html?title=Note+title&content=Note+content
W skrócie, to elementy adresu, które są podawane po znaku zapytania „?”. - GET – metoda protokołu HTTP, służy do pobierania wskazanego zasobu np. strony www, może służyć do przekazywania danych pomiędzy kolejnymi stronami, dane przesyłane w adresie URL.
- POST – metoda protokołu HTTP, służy do przekazywania danych do serwera w ciele żądania, najczęściej dane wysyłane z formularza na stronie www lub żądanie REST.
- Żądanie – dane wysyłane na serwer www przez użytkownika/klienta za pomocą np. przeglądarki www.
- Odpowiedź – dane wysyłane do użytkownika/klienta przez back-end i serwer www.
Komunikacja klient-serwer – ogólne pojęcie opisujące model komunikacji sieciowej między klientem np. aplikacja www, desktop, mobilną, która korzysta z zasobów, usług udostępnionych przez serwer np. pliki, przelewy bankowe.
Dodatkowo zachęcam do uzupełnienia wiedzy o takie elementy jak:
- Representational state transfer – architektura REST.
- Application programming interface – API.
- Usługi sieciowe – WebService.
- Aplikacja po stronie serwera – Web Application.
- Jak napisać aplikacje web z użyciem Spring MVC.
Zdjęcie główne artykułu pochodzi z unsplash.com.