Spring Autor pierwotnej wersji ćwiczenia: Michał Matłoka Celem ćwiczenia jest przygotowanie prostej aplikacji internetowej wykorzystującej architekturę Spring. Do wykonania ćwiczenia potrzebne jest zintegrowane środowisko programistyczne Springe Tool Suite (STS). W ćwiczeniu wykorzystano serwer aplikacji Pivotal tc Server zintegrowany z środowiskiem STS oraz bazę danych typu in-memory H2. Wymagane oprogramowanie można znaleźć na poniższych stronach, również w formie archiwów.zip: https://spring.io/tools/sts/all (zip) http://www.h2database.com/html/download.html (zip) Spring Framework oraz Spring MVC oferują kilka sposobów konfiguracji projektu. W tutorialu generalnie preferowana jest konfiguracja poprzez adnotacje (nowsze rozwiązanie), ale wykorzystywana jest też konfiguracja poprzez pliki XML (rozwiązanie tradycyjne). W ćwiczeniu nie jest wykorzystywany Spring Boot. 1. Uruchom narzędzie Spring Tool Suite. Wskaż lokalizację nowego obszaru roboczego (Workspace). 2. Z poziomu panelu Servers prawym klawiszem myszy uruchom serwer aplikacji Pivotal tc Server. 3. Upewnij się czy serwer działa - odwiedź stronę http://localhost:8080/. 4. Uruchom bazę H2 - po rozpakowaniu w katalogu bin znajdziesz plik h2.bat. Po pomyślnym uruchomieniu w oknie przeglądarki pojawi się strona z panelem bazy H2. W przeciwnym wypadku najprawdopodobniej brakuje aplikacji java na ścieżce PATH systemu. 5. W środowisku STS otwórz perspektywę Database Development (Window Perspective Open Perspective Other Database Development). Uwaga: Jeśli ta perspektywa nie jest dostępna, należy doinstalować wtyczkę Database Development (Help Install New Software)
6. W panelu Data Source Explorer kliknij prawym przyciskiem myszy na Database Connections New... Wybierz profil Generic JDBC. Podaj nazwę H2 i kliknij Next. 7. Dodaj sterownik JDBC dla bazy H2. Wybierz ikonkę z plusem. 8. W zakładce Name/Type zaznacz Generic JDBC Driver. Przejdź do zakładki JAR List. Wybierz Add JAR/Zip i wskaż plik.jar znajdujący się w katalogu bin rozpakowanej bazy H2. W zakładce Properties wybierz pole Driver Class. Kliknij przycisk "..." który się pojawi. Wybierz Browse for class i wybierz org.h2.driver. Kliknij OK, a następnie w oknie dodawania sterownika JDBC również OK.
9. Ustaw parametry połączenia zgodnie z poniższym zrzutem ekranu. Przetestuj czy połączenie działa przyciskiem Test Connection. Zamknij okno przyciskiem Finish. 10. Poprzez Data Source Explorer możesz przeglądać zawartość bazy danych, edytować zawarte dane. Wykonaj polecenie DDL tworzące tabelę EMPLOYEES z trzema kolumnami: ID NUMERIC(4) NOT NULL PRIMARY KEY, NAME VARCHAR(100) NOT NULL i POSITION VARCHAR(100) NOT NULL. By to zrobić kliknij wskazany na poniższym ekranie przycisk Open scrapbook to edit SQL statements.
11. W otwartym scrapbooku sprawdź czy wybrana jest właściwa nazwa połączenia: 12. Wprowadź samodzielnie polecenie CREATE TABLE a następnie je wykonaj (opcją Execute All z menu kontekstowego edytora). 13. Zobacz czy w Data Source Explorer widoczna jest tabela EMPLOYEES. 14. Wstaw do tabeli kilka wierszy odpowiednimi poleceniami SQL. Po ich wykonaniu możesz zamknąć otwarte scrapbooki. 15. Przejdź z powrotem do perspektywy Spring (Window Perspective Open Perspective Other Spring (Default)).
16. Z menu głównego wybierz File New Spring Legacy Project ( Legacy gdyż nie chcemy korzystać ze Spring Boot). Następnie wybierz rodzaj szablonu projektu - w tym wypadku Spring MVC Project. Szablon zostanie pobrany z repozytorium Maven'a. 17. Po przejściu do kolejnego ekranu przyciskiem Next, podaj nazwę projektu SpringLab i nazwę głównego pakietu projektu pl.lab.spring. Kliknij przycisk Finish. 18. Z menu kontekstowego węzła projektu w panelu Package Explorer wybierz Maven Update Project... i kliknij OK w oknie dialogowym, które się pojawi.
19. Uruchom projekt (prawy przycisk myszy na węźle projektu: opcja Run As Run On Server). W okienku, które się pojawi, wybierz właściwy serwer i zaznacz opcję Always use this server when running this project. Kliknij przycisk Next. 20. W kolejnym kroku powinny zostać pokazane projekty, które zostaną uruchomione na serwerze. Po prawej stronie powinien być widoczny projekt SpringLab. Zamknij okno przyciskiem Finish. 21. W jednym z paneli środowiska otworzy się strona uruchomionej aplikacji. Oczekiwany efekt to napis Hello world! wraz z aktualną datą.
22. Obejrzyj strukturę projektu. Wybrany przez nas w STS szablon projektu wykorzystuje narzędzie do budowania aplikacji Maven. Najważniejszy plik w projekcie Mavena to pom.xml. Zawiera on opis projektu, konfigurację budowania a także wszystkie zależności. W przypadku modyfikacji powyższego pliku czasami może być wymagane odświeżenie projektu w środowisku programistycznym poprzez prawy przycisk myszy na nazwie projektu, Maven Update Project... a. Odszukaj plik web.xml. Obejrzyj konfigurację serwletu dispatchera. b. Odszukaj plik servlet-context.xml. Zauważ iż projekt został skonfigurowany do użycia adnotacji. c. Odszukaj plik HomeController.java. i. Zwróć uwagę na adnotację @Controller ii. Obejrzyj kod metody home() i zwróć uwagę na jej adnotację. Adnotacja @RequestMapping definiuje typ zapytania i odwzorowujący adres. Może zostać użyta zarówno do pojedynczej funkcji jak i całej klasy kontrolera. iii. Zwróć uwagę na parametr metody model i co jest za jego pośrednictwem przekazywane do widoku. iv. Obejrzyj widok odpowiadający danemu kontrolerowi - home.jsp. W jaki sposób definiowane jest w kontrolerze, który widok mu odpowiada? (odpowiedzią na to pytanie jest wartość zwracana przez metodę akcji w połączeniu z konfiguracją serwletu dispatchera) 23. Utwórz nowy kontroler (prawy przycisk myszy na pakiecie pl.lab.spring New Class). Nadaj mu nazwę CounterController. Oznacz klasę odpowiednią adnotacją. 24. Zrefaktoryzuj strukturę projektu - przenieś oba kontrolery do nowego pakietu. By to zrobić zaznacz oba i wybierz z menu kontekstowego opcję: Refractor Move Create new package pl.lab.spring.controllers. 25. Teraz utworzymy usługę z której będzie korzystać CounterController. Utwórz nową klasę CounterService w pakiecie pl.lab.spring.services. Oznacz klasę adnotacjami @Singleton (standardowa adnotacja Javy) i @Service (stereotypowa adnotacja Springa). Pierwsza z nich określa zasięg obiektu, druga wskazuje, że jest to obiekt bean (ziarno) Springa. Dodaj do klasy prywatne pole typu int o nazwie counter. Dodaj publiczną metodę count() zwracającą zinkrementowaną wartość zmiennej count. Wróć do CounterController. Dodaj poniższy kod: @Autowired private CounterService counterservice; @RequestMapping("/counter") public ModelAndView getcounter() { ModelAndView mav = new ModelAndView("counter"); mav.addobject("counter", counterservice.count()); return mav; Dzięki adnotacji @Autowired obiekt CounterService będzie automatycznie "wstrzyknięty" do CounterController.
26. Utwórz widok dla kontrolera CounterController. Dodaj do src/main/webapp/web- INF/views nowy plik.jsp (New -> Other -> Web -> JSP File). Jako nazwę podaj counter.jsp 27. Zamień zawartość pliku counter.jsp na poniższy kod: <%@ page language="java" contenttype="text/html; charset=iso-8859-1" pageencoding="iso-8859-1"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"> <title>stan licznika</title> </head> <body> <p>stan licznika: ${counter</p> </body> </html> 28. Uruchom aplikację. Przejdź do http://localhost:8080/spring/counter. 29. Zaobserwuj z użyciem 2 przeglądarek jaki jest zakres kontrolera i podłączonej pod niego usługi. 30. CounterService został oznaczony jako @Singleton. Spring oferuje również kilka innych zasięgów obiektów. Zamień @Singleton na @Scope("session"). Uruchom aplikację. Czy umiesz zinterpretować komunikat o błędzie? 31. Kontrolery w Spring MVC domyślnie są singletonami. Nie można w Spring bezpośrednio wstrzyknąć beana jeśli bean wstrzykiwany ma mniejszy zasięg niż bean do którego odbywa się wstrzyknięcie. W celu potwierdzenia diagnozy dodaj adnotację @Scope("session") również do kontrolera CounterController. Przetestuj działanie aplikacji. 32. Usuń adnotację @Scope z kontrolera. Z punktu widzenia skalowalności aplikacji lepiej aby kontroler był singletonem. Problem wstrzyknięcia beana o zasięgu sesji do kontrolera będącego singletonem rozwiążemy w kolejnym punkcie poprzez wykorzystanie obiektu proxy. 33. Zmień adnotację @Scope klasy CounterService na @Scope(value = "session", proxymode = ScopedProxyMode.TARGET_CLASS). Atrybut dodany do adnotacji zasięgu sprawi, że kontener Spring wygeneruje klasę proxy, której instancja będzie wstrzykiwana zamiast instancji klasy CounterService. Instancja klasy proxy będzie przekazywać wywołania publicznych metod do właściwej dla danej sesji użytkownika instancji klasy CounterService.
34. Dla ustawienia ScopedProxyMode.TARGET_CLASS generacja klasy proxy odbywa się z wykorzystaniem biblioteki CGLIB. Dodaj poniższy wpis wskazujący tę bibliotekę jako jedną z wymaganych przez naszą aplikację do pliku pom.xml (w sekcji <dependencies>): <dependency> <groupid>cglib</groupid> <artifactid>cglib</artifactid> <version>3.2.5</version> </dependency> 35. Wskazana biblioteka musi być pobrana z repozytorium Mavena. Z menu kontekstowego węzła projektu w panelu Package Explorer wybierz Maven Update Project... i kliknij OK w oknie dialogowym, które się pojawi. 36. Uruchom aplikację i ponownie przetestuj zachowanie licznika w dwóch przeglądarkach. 37. W zakresie komunikacji z bazą danych Spring Framework obsługuje odwzorowanie obiektowo-relacyjne (wspiera m.in. JPA i Hibernate). W projektach w których jednym z najważniejszych aspektów jest wydajność wciąż jednak korzysta się z JDBC. Ułatwienie korzystania z JDBC jest jedną zalet frameworka Spring. Dlatego w naszej ćwiczebnej aplikacji wykorzystamy klasy Spring Framework wspierające podstawowe operacje na relacyjnej bazie danych przez JDBC. 38. Wymagane jest dodanie dwóch zależności do projektu - pakietu spring-jdbc oraz pakietu bazy H2 zawierającego sterownik JDBC. Otwórz plik pom.xml. Dodaj poniższy kod do zawartości elementu <dependencies>. Upewnij się że wersja serwera H2 wskazana jako <dependency> faktycznie odpowiada wykorzystywanej wersji serwera. Jeśli nie, dokonaj stosownej poprawki. <dependency> <groupid>org.springframework</groupid> <artifactid>spring-jdbc</artifactid> <version>${org.springframework-version</version> </dependency> <dependency> <groupid>com.h2database</groupid> <artifactid>h2</artifactid> <version>1.4.194</version> </dependency> 39. Odśwież konfigurację projektu tak jak wcześniej po dodaniu biblioteki CGLIB. Wymagane zależności zostaną automatycznie pobrane.
40. Utwórz nową klasę Employee w nowym pakiecie pl.lab.spring.entity. Dodaj do klasy trzy pola prywatne: jedno typu int o nazwie id oraz dwa typu String o nazwach name i position. Korzystając z narzędzi zawartych w środowisku wygeneruj na podstawie pól konstruktor (menu kontekstowe: Source Generate Constructor using fields). Wygeneruj metody typu setter i getter dla wszystkich trzech pól (menu kontekstowe: Source Generate Getters and Setters). Sformatuj kod (ctrl+shift+f). 41. W pliku src/main/webapp/web-inf/spring/root-context.xml dodaj poniższy kod <bean id="datasource" class="org.springframework.jdbc.datasource.drivermanagerdatasource"> <property name="driverclassname" value="org.h2.driver" /> <property name="url" value="jdbc:h2:tcp://localhost/~/test;db_close_delay=-1" /> <property name="username" value="sa" /> </bean> 42. Powyższy kod tworzy bean (ziarno) Springa zawierający źródło danych JDBC reprezentujące uruchomioną wcześniej bazę danych H2. 43. Dodaj nową klasę EmployeeDAO w pakiecie pl.lab.spring.dao. Oznacz klasę adnotacją stereotypową Springa @Repository. Dodaj w klasie poniższy kod, a następnie uzupełnij wymagane importy. private EmployeeMappingQuery employeemappingquery; private static class EmployeeMappingQuery extends MappingSqlQuery<Employee> { private EmployeeMappingQuery(DataSource ds) { super(ds, "select * from employees"); @Override protected Employee maprow(resultset rs, int rownumber) throws SQLException { return new Employee( rs.getint("id"), rs.getstring("name"), rs.getstring("position")); @Resource public void setdatasource(datasource datasource) { this.employeemappingquery = new EmployeeMappingQuery(dataSource); public List<Employee> getall() { return employeemappingquery.execute();
44. Powyższy kod tworzy klasę wewnętrzną typu MappingSqlQuery która dla zapytań SQL dokonuje konwersji uzyskiwanych przez zapytanie JDBC zbiorów wynikowych ResultSet na obiekty klasy Employee. Data source skonfigurowany w pliku XML jest wstrzykiwany poprzez adnotację @Resource do klasy. Zauważ, że tym przypadku wykorzystaliśmy wstrzykiwanie jako argument metody. W CounterController wstrzykiwaliśmy bezpośrednio do pola klasy. Tu jednak wstrzykiwany obiekt jest używany w konstruktorze innego obiektu, który tworzymy. 45. Utwórz nowy kontroler EmployeeController. Korzystając z adnotacji @Autowired dodaj pole zawierające klasę EmployeeDAO. Dodaj metodę mapowaną na adres /employees analogicznie do klasy CounterController. W tworzonym modelu umieść wynik metody getall() obiektu EmployeeDAO. 46. Dodaj widok dla danego kontrolera. Wyświetl w nim zawartość modelu korzystając z poniższych fragmentów kodu: <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <table border="1"> <tr> <th>name</th> <th>job</th> </tr> </table> <c:foreach var="emp" items="${employees"> <tr> <td><c:out value='${emp["name"]' /></td> <td><c:out value='${emp["position"]' /></td> </tr> </c:foreach> 47. Sprawdź działanie stworzonego widoku. Uwagi końcowe na temat Spring i Spring MVC: Spring (w tym Spring MVC) oprócz konfiguracji poprzez adnotacje umożliwia również konfigurację poprzez pliki XML Aplikacje webowe w Spring mogą wykorzystywać inne frameworki MVC niż Spring MVC oraz różne technologie widoku (nie tylko JSP) Wiele bibliotek i frameworków oferuje integrację ze Spring Framework, umożliwiając wykorzystanie ich obiektów poprzez mechanizm wstrzykiwania zależności (dependency injection) Do tworzenia projektów bazujących na frameworku Spring obecnie często jest wykorzystywany Spring Boot (ułatwia konfigurację projektu, szczególnie przydatny przy tworzeniu mikroserwisów)