Spring MVC: próba przeanalizowania cyklu żądania i odpowiedzi HTTP


Mapka poniżej przedstawia pewną strukturę, która w uproszczeniu oddaje to co się dzieje pod maską aplikacji opartej o Spring MVC gdy zostaje do niej nadesłany request HTTP. Jeżeli korzystasz z Intellij IDEA, to niektóre z elementów mapki możesz odszukać w swoim projekcie używając skrótu ctrl+n – miłego studiowania 😉 .

  1. DispatcherServlet – pełni rolę Front Controllera w Spring MVC. Przez niego przechodzą w pierwszej kolejności wszystkie żądania HTTP, jego misją jest dostarczyć każdy dany request do odpowiedniego kontrolera.  Jednakże będąc dyspozytorem zarządzającym ruchem zapytań, nie ustala sam wszystkiego. Na podstawie zawartości przychodzącego żądania nie jest w stanie samodzielnie określić do jakiego kontrolera ma wysłać dane żądanie. Tak więc aby to zrobić przesyła zapytanie do odpowiedzialnego za to komponentu.
  2. HandlerMapping – na podstawie zawartości otrzymanego żądania, tj. metody HTTP (POST, GET…), adresu URL lub jego parametrów, jest w stanie ustalić kontroler do którego ma trafić request. Gdy to się stanie, żądanie wraz z namiarem na określony kontroler wraca z powrotem do dispatchera.
  3. DispacherServlet wysyła request do właściwego kontrolera, tj. obiektu klasy oznaczonej adnotacją @Controller, żyjącego na czas działania aplikacji jako jej komponent w kontenerze Springa.
  4. Controller rozkłada request na części, wyciągając z niego istotne dane jak metody HTTP, parametry URL, pola formularza. W następnej kolejności wywołuje określoną akcję, którą przesyła do modelu, czyli w kierunku logiki biznesowej modelującej dane, i samych danych.
  5. Model zgodnie z wzorcem MVC ogólnie, nie tylko w Spring MVC, są to dane oraz metody używane do pracy nad nimi. W odniesieniu więc do Spring MVC, zaliczyłbym do niego, logikę biznesową, zawartą w warstwie pośredniej, tzw. serwisowej, oraz warstwę persystencji – encje i metody operujące na bazie danych.
    ——————————————————————-
    Cytat:
    “Spring MVC implements the popular Model-View-Controller pattern(…).
    Model represents your domain objects.”
    Mastering Spring framework 5, Part 1: Spring MVC By Steven HainesJavaWorld 26-07-2018
    ——————————————————————-
    Dla mnie obiekty domenowe, to tacy główni aktorzy aplikacji, których stan zwykle jest zapisywany w bazie danych, a ich klasy są mapowane na tabele w bazie. Przykładowo jeżeli twoja aplikacja będzie rejestrem trzymającym kontrakty, to obiektami domenowym będą poszczególne z nich. 
    Klasy i metody warstwy serwisowej służą do obróbki danych. Uczestniczą one w tworzeniu pewnego modelu danych, który następnie za pomocą metod warstwy persystencji, zostaje utrwalony w bazie. Jeżeli więc, na froncie jest przycisk z napisem pomnóż to co widzisz razy pięć, to w warstwie serwisowej zostanie wykonane mnożenie, a następnie po przez użycie odpowiedniej metody warstwy persystencji, wartość zostanie nadpisana w bazie danych. Warstwa usług, powinna umieć realizować wszystkie operacje przekazane przez kontroler, na podstawie żądań HTTP płynących z frontu. Natomiast warstwa persystencji, powinna umieć wykonać adekwatną akcję na bazie danych, aby utrwalić w niej to co zostało przekazane. W następnej kolejności odświeżony stan obiektów, po ujęciu zmian, wraca np. w formie listy z powrotem do kontrolera.
  6. Controller opakowuje dane wracające z modelu w… model? Otóż, w kontrolerze tworzony jest pewnego rodzaju obiekt wynikowy zwany modelem, będący rodzajem mapy. Składa się z opisu, który pełni rolę klucza oraz obiektu z danymi. Dokładniej idzie tu o interfejs org.springframework.ui.Model oraz jego implementację w klasie org.springframework.ui.Model.ExtendedModelMap. Przyznam, że czułem się tu nieco zakłopotany, bo nie pasował mi ten model, tj. obiekt który zwraca kontroler do modelu, w rozumieniu wzorca MVC – występuje tu zbieżność nazw. W każdym razie kojarzę sobie teraz ten obiekt, tj. model zwracany przez kontroler, jako pewną reprezentację danych zawartych w warstwie modelu.

    Przykład:

    @Controller
    public class CucumberController {
    
        @Autowired
        CucumberService cucumberService;
    
        @GetMapping("/Cucumbers")
        public String getCucumbers(Model model) {
            List<Cucumber> allCucambers = cucumberService.getAllCucumbers();
            model.addAttribute("cucumbers", allCucumbers);
            return "logicNameOfModel";
        }
    }

    Adnotacja @GetMapping nad metodą mapuje ją na obsługę żądania z użyciem metody HTTP – GET, oraz na  adres/identyfikator URI zasobu, pod którym będzie widoczny zwracany model, lokalnie domyślnie było by to http://localhost:8080/Cucumbers. Następnie do referencji typu List (interfejs), sparametryzowanej na obiekty Cucamber, przypisana jest metoda, która powinna zwrócić listę ogórków z bazy. Woła ona jednak wpierw do warstwy pośredniej, serwisowej, bo zwykle kontroler nie powinien kontaktować się bezpośrednio z warstwą persystencji. Powinny to robić właśnie klasy warstwy serwisowej, gdzie znajduje się również przestrzeń na dokonywanie ewentualnych manipulacji na danych, biegnących na widok, bądź w kierunku utrwalenia ich w bazie. Można tu przykładowo dokonywać jakichś kalkulacji, stan obiektów odchudzić o ileś pół, czy łączyć z innymi obiektami. Następnie referencja z namiarem na listę obiektów jest pakowana do modelu z opisem “cucumbers”, a na koniec metoda zwraca string “logicNameOfModel”. To co zwraca metoda, jest nazwą logiczną modelu, będącą jednocześnie w tym przypadku nazwą pliku logicNameOfModel.html – trzeba utworzyć taki plik w katalogu resourcesJeżeli zostanie on uzbrojony w obsługę silnika szablonów jak Thymeleaf , “rozumiejącego” się z Javą i Spring, to będzie zdolny renderować nadesłany z kontrolera model na widok dla użytkownika końcowego. Wewnątrz natomiast pliku formularza, dostęp do obiektów zapakowanych w model metodą addAttribute(), będzie możliwy poprzez nazwy atrybutów/kluczy, w tym wypadku “cucumbers”.

  7. DispatcherServlet otrzymuje model z kontrolera aby przekazać go na widok, jednak nie potrafi na podstawie nazwy logicznej modelu określić położenia pliku, gdzie miałby go wysłać.
  8. ViewResolver otrzymując model, na podstawie jego nazwy logicznej – logicNameOfModel, potrafi on zlokalizować ścieżkę do położenia odpowiedniego pliku z widokiem.
  9. DispatcherServlet przekazuje model do pliku z widokiem, który dla niego zlokalizował View Resolver  logicNameOfModel.html
  10. View – plik z widokiem dla użytkownika końcowego, musi potrafić przyjąć i poprawnie z renderować dane wstrzyknięte mu przez DispacherServlet do wynikowej postaci, zrozumiałej dla przeglądarek – kodu html. Wiąże się to z dołożeniem zależności do pom.xmlaplikacji z biblioteką, umożliwiającą te zadania. Zakładając, że wykorzystywany jest Thymeleaf, plik będzie dokumentem html z zaszytym wewnątrz, nad sekcją <head>, w tagu <html> namespace:
    <html xmlns:th=”http://www.thymeleaf.org”>. No i tyle w sumie styka, żeby można było w nim używać składni Thymeleaf, która btw. opiera się o składnię Expression Launguage. Daje ona możliwość dostępu do pól, iterowania po przekazanych obiektach za pomocą pętli, tworzenia warunków i wiele więcej. Thymeleaf aktualnie stał się domyślną, wbudowaną w Spring Boot biblioteką… Kwestia na osobny temat, choć być może w pracy na “back-endzie”, mniej istotny. Systemy Java aktualnie zdaje się zwykle zwracają w ciałach odpowiedzi HTTP dane w formacie JSON, a pisaniem klientów dla użytkownika końcowego zajmują się już inni programiści. Anyway, jeżeli chcesz się zaznajomić z Thymeleaf, raczej nie ma co tracić czasu na zbędne wyszukiwania (ja go trochę straciłem), a od razu przejść do ich tutoriala online i porobić trochę zadań, a resztę się już douczać zgodnie z potrzebą.
  11. DispatcherServlet – gotowy widok ponownie wraca do dyspozytora ruchu, który przekazuje go do użytkownika końcowego. 
  12. Web Browser – użytkownik przegląda ogórki 😉 .

Warning! Tego nie pisał programista, ale dev-wannabe. Jak coś poknociłem, chętnie się o tym dowiem.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *