System, którego utrzymaniem i rozwojem zajmuję się na co dzień to w większości procesy backend'owe - logika biznesowa, cykliczne procesy wykonywane automatycznie w tle. Oczywiście system posiada również interfejs użytkownika, ale nigdy przy jego tworzeniu nie przykładaliśmy większej wagi do jego użyteczności i ergonomiczności. Jego podstawy tworzone były ładnych kilka lat temu i daleko mu do funkcjonalności obecnie popularnych interfejsów web'owych.
Ostatnio trafiło do mnie zadanie które oprócz modyfikacji procesów biznesowych wymagało również przygotowania odpowiedniego GUI. Uznałem zatem że nadarzyła się świetna okazja do poznania kilku nowych rzeczy i przyjrzenia się wreszcie trochę dokładniej tematowi w jaki sposób Spring MVC może współpracować z jQuery i z Ajaxem. Przejrzałem kilka przykładów, for internetowych. W tym poście postaram się zebrać tą wiedzę w całość i przedstawić na jak najprostszym przykładzie w jaki sposób zmusić kontrolery Spring'owe do obsługi asynchronicznych requestów i jak je obsługiwać zarówno po stronie serwera jak i klienta.
Na początek utwórzmy aplikację HelloWorld w SpringMVC.
Do budowania aplikacji wykorzystamy maven'a, zatem musimy przygotować odpowiedni plik pom.xml, który powinien wyglądać tak:
Aplikację nazwiemy springAjax.
Ja wykorzystałem wersję spring'a 3.0.5, ale oczywiście nic nie stoi na przeszkodzie aby użyć innej. W pliku pom.xml wystarczy podać bibliotekę spring-webmvc. Pozostałe wymagane biblioteki spring'a zostaną dociągnięte automatycznie przy aktualizacji zależności.
W następnej kolejności zdefiniujmy plik web.xml dla naszej aplikacji.
Aby requesty obsługiwane były przez odpowiednie kontrolery spring'owe, należy zdefiniować servlet org.springframework.web.servlet.DispatcherServlet i określić odpowiedni pattern, który będziemy wykorzystywać w naszej aplikacji. Załóżmy zatem że wszystkie requesty o masce *.mvc będą obsługiwane przez Spring'a.
Zawartość tego pliku dla aplikacji helloWorld powinna wyglądać następująco:
Pora na zdefiniowanie widoku.
W katalogu WEB-INF załóżmy katalog jsp/ w którym będziemy umieszczać nasze pliki.
W nim zakładamy plik springAjax.jsp o zawartości:
W kolejnym kroku musimy zdefiniować plik konfiguracyjny do SpringMVC.
Można to zrobić na dwa sposoby - wskazać plik konfiguracyjny w pliku web.xml, lub utworzyć plik o nazwie której domyślnie poszukuje Spring, czyli nazwaServletu-servlet.xml. Jako że robimy wersję najprostszą wykorzystamy wersję drugą, czyli w katalogu WEB-INF tworzymy plik o nazwie springAjax-servlet.xml. Jego zawartość powinna być następująca:
W pliku określamy pakiety które będą skanowane pod kątem obecności beanów spring'owych.
W naszym przypadku będą to :
- pl.decerto.controller (w którym będziemy umieszczać kontrolery)
- pl.decerto.service (w którym będziemy umieszczać dodatkowe serwisy).
Dodatkowo musimy zdefiniować bean ContentNegotiatingViewResolver który wyszukuje odpowiedni widok na podstawie requestu.
Określamy, że wszystkie requesty z rozszerzeniem *.mvc będą typu text/html.
Definiujemy domyślny resolver do widoków UrlBasedViewResolver oraz prefix i suffix.
Mamy kompletną konfigurację.
W następnym kroku tworzymy kontroler. Umieścimy go w pliku pl.decerto.controller.SpringAjaxController
Adnotacja @Controller określa, że klasa zawiera kontroler Spring MVC
Adnotacja @RequestMapping określa pod jakim URL'em dostępny będzie nasz kontroler. Tą adnotację możemy stosować zarówno dla całej klasy jak i dla poszczególnych metod. W podanym przykładzie zdefiniowałem URL na poziomie klasy. Dodatkowo na poziomie funkcji określiłem jaki parametr URL'a zdecyduje o tym że dana funkcja zostanie wywołana oraz jaką metodą będzie można ją wywołać (w tym przypadku GET). Funkcja nie robi nic innego oprócz zwrócenia nazwy widoku jaki ma zostać wyświetlony (czyli "springAjax").
Jako że w pliku springAjax-servlet.xml określiliśmy wcześniej prefix "WEB-INF/jsp" oraz suffix ".jsp", podanie nazwy widoku "springAjax" spowoduje wyświetlenie pliku "WEB-INF/jsp/springAjax.jsp"
To już cała konfiguracja aplikacji HelloWorld w springMVC. Możemy zbudować aplikację, deploy'
ować na przykład w Apache Tomcat i uruchomić.
Po uruchomieniu Tomcat'a i w pisaniu w przeglądarkę adresu:
powinniśmy zobaczyć "HELLO WORLD!"
Po tym przydługim wstępie możemy przystąpić do właściwej części, czyli do wykorzystania Ajax'a.
Obsługa ajaxa odbywa się za pomocą formatu JSON (JavaScript Object Notation). W uproszczeniu można powiedzieć że mechanizm ten służy do serializacji i deserializacji obiektów.
Potrzebujemy zatem mechanizmów do obsługi JSON'a zarówno po stronie klienta (JavaScript) jak i po stronie serwera (SpringMVC).
Po stronie klienta wykorzystamy do tego chyba najpopularniejszą bibliotekę JavaScriptową JQuery w połączeniu z plugin'em serializeForm, który można znaleźć pod tym adresem:
Tworzymy zatem w katalogu webapp naszej aplikacji folder "js" i wrzucamy do niego serializeForm.js oraz jquery i jquery-ui, które przyda nam się do utworzenia okienka dialogowego. Pakiet znajdziemy tutaj:
Po stronie serwera natomiast wykorzystamy bibliotekę Jackson. Informacje o niej znajdziemy na stronie:
Aby dodać ją do projektu wystarczy dopisać do pliku pom.xml następujące zależności:
i wykonać mvn install
Odpowiednie biblioteki powinny zostać dociągnięte do projektu
Następnie musimy zmusić SpringMVC żeby poprawnie obsługiwał requesty json'owe. Dodajemy zatem do pliku springAjax-servlet.xml definicję dwóch bean'ów, które odpowiadają poprawną serializację i deserializację danych do formatu JSON:
Dzięki temu nasz kontroler spring'owy będzie poprawnie obsługiwał mediaType application/json.
Załóżmy zatem, że chcemy zrobić formularz, za pomocą którego tworzymy prostą listę użytkowników jakiegoś systemu. Na jednym ekranie zrobimy listę dodanych użytkowników, przycisk do dodawania nowej pozycji, po wciśnięciu którego pojawi się okienko dialogowe z odpowiednim formularzem.
Zatwierdzenie formularza spowoduje dodanie nowego wiersza do tabelki. Dodatkowo w okienku dialogowym pojawi się combo box, który również będzie wypełniany ajax'em (trochę "na siłę", ale chodzi o pokazanie przykładu :) )
Nasz plik jsp będzie wyglądał zatem tak:
Pojawiły nam się include'y javascript'ów i stylu CSS do okienka dialogowego (który powinien być pobrany wraz z jquery-ui, oraz include pliku springAjax.js.
Pojawiła się również tabelka, z <tbody id="userTable"> do którego dopisywać będziemy nowe wpisy.
Pojawił się <div id="dialog-form"> w którym zawarty jest formularz <form id="userForm"> w którym wypełniać będziemy dane użytkownika.
W pliku springAjax.js umieściłem wszystkie funkcje JS związane z obsługą requestów ajax'owych i obsługą okienka dialogowego.
Zawartość pliku springAjax.js jest następująca:
Zmodyfikowałem również kontroler, który obecnie ma postać następującą:
Przejdźmy zatem do obsługi najprostszego requestu ajax'a - wypełnimy combo z rolami na podstawie danych pobranych asynchronicznie z serwera.
W tym celu wykorzystamy funkcję refreshRoles() którą utworzyłem wcześniej w pliku springAjax.js
funkcja $.getJSON jest wbudowana w JQuery i służy do pobrania danych z serwera w postaci JSON za pomocą requestu typu GET. Funkcja callback (wykonywana po zrealizowaniu requestu asynchronicznego) czyści nasze combo o id="role", iteruje po otrzymanych elementach i dodaje odpowiednie opcje do combo. W tym przypadku wywołana została metoda kontrolera o nazwie loadRoles().
W funkcjach kontrolera obsługujących requesty ajax kluczowa jest adnotacja @ResponseBody. Spring otrzyma request, rozpoznaje że odpowiedź powinna być serializowana do postaci JSON (wykorzystany jest zdefiniowany wcześniej w konfiguracji bean jacksonMessageConverter), i odesłana bezpośrednio jako responseBody, z pominięciem standardowych nagłówków HTTP.
Wykorzystana klasa RoleDTO wygląda następująco (najprostszy obiekt słownikowy):
Dane wprowadzanych użytkowników będziemy przechowywać w obiektach typu UserDTO o następującej postaci:
Dodatkowo utworzyłem serwis o nazwie SpringAjaxService.java, w którym umieściłem metody zarządzające danymi. Jego zawartość jest następująca:
W serwisie utworzyłem statyczną listę obiektów UserDTO aby w najprostszy sposób zrobić przechowywanie obiektów po stronie serwera. Oczywiście w praktyce tak nie robimy :). Utworzyłem metody do zapisywania, usuwania, pobierania danych pojedynczego obiektu UserDTO, które wykorzystywane są przez kontroler do obsługi requestów. Funkcja getRoles() zwraca nam zawartość naszego dynamicznego combo. W praktyce byłoby tu prawdopodobnie pobieranie listy z bazy danych.
W tym momencie możemy uruchomić aplikację, wywołać adres:
i w przeglądarce zobaczymy:
[{"id":1,"name":"Administrator"},{"id":2,"name":"Obsługa klienta"},{"id":3,"name":"Dyrektor"}]
czyli serializowaną listę obiektów RoleDTO.
W tym momencie po uruchomieniu aplikacji linkiem:
http://localhost:8080/springAjax/springAjax.mvc?showUsers
otrzymamy przycisk "Dodaj Użytkownika" po wciśnięciu którego otworzy się okienko dialogowe z combo wypełnionym przez request ajax'owy:
Umiemy już odebrać dane ajax'em, pora zatem na wysłanie danych do serwera.
Przytoczę tutaj ponownie fragment kodu z pliku springAjax.js, który podpięty jest pod przycisk "Dodaj":
Na początku formularz "userForm" musi zostać serializowany do postaci Javascript'owej. Właśnie do tego wymagany jest dodatkowy plugin serializeForm.js który dołączaliśmy do naszego pliku jsp.
Do wysłania requestu wykorzystujemy funkcję $.ajax z JQuery, w której określamy typ requestu (POST), url (springAjax.mvc), dataType (json), contentType (application/json) oraz przesłyłane dane - i tutaj musimy zamienić Javascript'ową strukturę danych do formatu JSON. Wykorzystujemy do tego funkcję JSON.stringify. W większości nowych przeglądarek ta funkcja jest wbudowana do silnika JavaScript. Dla starszych przeglądarek potrzebowalibyśmy dodatkowej biblioteki którą można znaleźć pod adresem http://www.json.org/js.html. Określamy oczywiście funkcje które mają zostać wywołane w przypadku sukcesu (zamknięcie okienka dialogowego oraz odświeżenie tabelki z listą osób) i w przypadku błędu (alert). Zwróćcie uwagę na jedną ważną rzecz. O ile w przypadku requestów typu GET podawaliśmy parametr, który określał metodę jaka zostanie wywołana, w przypadku requestu POST nie możemy wprost w URL'u zdefiniować tej wartości. Można poradzić sobie z tym na kilka sposobów. Najprostszy zastosowałem w tym przykładzie - w kontrolerze jest tylko jedna metoda typu POST, więc SpringMVC nie ma problemu z wywołaniem odpowiedniego kodu.
Można również zastosować ukryty parametr w formularzu, ale jest to trochę problematyczne, bo w naszym przypadku wystąpiłyby problemy z serializacją takiego formularza. Można również w adnotacji @RequestMapping określić dedykowany URL dla konkretnej metody typu POST i wtedy w URL'u ajaxowego wywołania musielibyśmy zamiast springAjax.mvc wpisać na przykład saveUser.mvc.
Przy tak zdefiniowanej funkcji po wciśnięciu przycisku "Dodaj" zostanie wywołana funkcja kontrolera o nazwie saveUser() która jako jedyna w kontrolerze obsługuje metodę POST. W tym przypadku istotna jest również adnotacja @RequestBody, która określa że na wejściu otrzymamy dane bez nagłówków a dodatkowo dane otrzymane przez metodę zostaną automatycznie zdeserializowane do obiektu typu UserDTO, ponieważ taki parametr przyjmuje metoda. Odbywa się to zupełnie przezroczyście.
W podanym przykładzie zdefiniowałem dodatkowo możliwość kasowania rekordów (metoda deleteUser()), edycji rekordów (fetchSingleUser() w połączeniu z omówioną chwilę wcześniej metodą saveUser()), oraz loadTable(), która zwraca wszystkie rekordy z których następnie budowana jest tabelka HTML. Wszystkie te metody działają na dokładnie takich samych zasadach jak opisane powyżej requesty, więc nie będę zagłębiał się już w ich szczegóły.
Dokładnie z działaniem aplikacji możecie zapoznać się uruchamiając przykład (na końcu znajdziecie link zarówno do gotowego war'a którego wystarczy wgrać do katalogu webapps Tomcat'a) , jak i przeglądając kompletny projekt, który po rozpakowaniu można zbudować poleceniem mvn install. Na potrzeby Tomcata do pom.xml dodana została zależność od biblioteki jstl której wymaga SpringMVC a Tomcat jej nie zawiera.
Zdaję sobie sprawę że miejscami opis jest zbyt szczegółowy a przykład jest dość oczywisty, ale założyłem że warto opisać to dość dokładnie chociażby dlatego że nie każdy na co dzień korzysta ze Spring MVC i nie każdy miał z nim jakąkolwiek styczność. Chciałem zatem zacząć od zupełnych podstaw tak aby każdy był w stanie w bardzo szybki sposób rozpocząć zabawę z tym na prawdę fajnym frameworkiem.
Generalnie pomimo tego że wpis jest dość długi, to temat został omówiony jedynie częściowo. Nie poruszyłem tutaj chociażby kwestii jakichkolwiek walidacji które mogą odbywać się zarówno po stronie klienta jak i serwera, ale to już temat na odrębny wpis.
Piki do pobrania:
War:
Źródła:
Brak komentarzy:
Prześlij komentarz