Spring MVC - temat obszerny i w wielu miejscach opisywany. Oto moje podejście.
Zacznijmy od bardzo prostej aplikacji pokazującej serce tego modułu.
1. Zależności:
2. web.xml :
Warto zwrócić uwagę na to, że jeśli zdefiniujemy jakiś BeanFactoryPostProcessor albo BeanPostProcessor w głównym kontekście to NIE ZADZIAŁA dla beanów z webowego kontekstu. W dokumentacji interfejsu BeanPostProcessor czytamy :
"Plain bean factories allow for programmatic registration of post-processors, applying to all beans created through this factory."
Podobnie jest dla BeanFactoryPostProcessor. Zatem jasne.
Jedna jeszcze istotna uwaga: otóż główny kontekst jest załadowany zanim zostaną zainicjowane serwlety.
3. Zajrzyjmy do wnętrza podanych konfigów springa
- root-context.xml mam pusty - aplikacja narazie nie ma żadnej logiki :-)
- mvc-dispatcher-servlet.xml:
Każde żądanie które będzie obsługiwane przez DispatcherServlet przechodzi cykl:
- na podstawie żadania http należy uruchomić jakąś metodę biznesową (oczywiście przy odpowiednim wypakowaniu danych z żądania),
- metoda może coś zwrócić,
- trzeba klientowi zwrócić wynik w takiej postaci jakiej oczekuje.
Dokładnie to robi dla nas DispatcherServlet
- odbiera żadanie,
- wywołuje "odpowiednią" metodę biznesową na kontrolerze. Kontroler to nic innego jak zwykły bean. W jaki sposób serwlet szuka owej "odpowiedniej" metody pokażę niżej (konfiguracja opatrzona komentarzem "1"),
- odbiera wynik tego wywołania,
- przekierowuje renderowanie odpowiedzi do widoku. Widokiem jest klasa implementująca interfejs View. Spring dostarcza pokaźną ich liczbę i ten temat zasługuje na osobny post. DispatcherServlet odwołuje się do ViewResolver-a aby wybrać odpowiedni widok i renderować odpowiedź. Tutaj posłużyłem się klasą InternalResourceViewResolver (komentarz "2") która oczekuje że kontroler zwróci nazwę widoku. Nazwa ta jest nazwą strony jsp (komentarz "4"); szukana jest w katalogu /WEB-INF/pages (komentarz "3").
Przykład:
Jeśli kontroler zwraca "hello", to InternalResourceViewResolver oczekuje istnienia strony /WEB-INF/pages/hello.jsp i ona zostanie odesłana jako odpowiedź.
Wiele rzeczy może tu być nadal niejasnych (np jak kontroler 'rozpakowuje' parametry żądania, jak zapisuje do odpowiedzi itd...). Na ten moment nie przejmujmy się tym i idźmy dalej.
4. Prosty kontroler:
- @Controller jest adnotacją markerową i jest podtypem @Component. Sama z siebie nic nie dodaje,
- @RequestMapping - owa adnotacja mówi dispatcher-owi że należy ją wywołać jak przyjdzie żądanie pasujące do "/hello" (przy mojej konfiguracji mapowania dispatcher-a w web.xml będzie to http://serwer:port/nazwa_aplikacji/hello). Adnotacji tej poświęcę również osobny post. Na razie niech nam wystarczy to co już wiemy.
Zapewne zauważyliście, że metoda sayHello otrzymuje w parametrze Model - jest to dekorator mapy do której możemy wkładać dane. W moim przykładzie pod kluczem "imie" trzymam zmienną name, do której odwołuję się na stronie hello.jsp:
git://github.com/slturo/mvc-start.git
1. Zależności:
<!-- Spring 3 dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- Servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <!-- core taglib --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>
2. web.xml :
<!-- tutaj definiujemy kontekst aplikacji. Możemy podać po przecinku wiele konfigów, wszystkie zostaną załadowane jako jeden kontekst. --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:/META-INF/root-context.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- tutaj konfigurujemy servlet springowy z własnym konfigem springa. --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/mvc-dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>Zwróćmy uwagę na bardzo ważną rzecz: skonfigurowaliśmy 2 konteksty springa. Roboczo ten skonfigurowany przez ContextLoaderListener nazwijmy głównym kontekstem, a ten drugi webowym. Są to dwa niezależne konteksty springa, przy czym kontekst webowy widzi główny kontekst jako parent. Czyli beany z kontekstu webowego mogą wstrzykiwać sobie beany z konteksu głównego. Należy dodać, że możemy mieć wiele DispatcherServlet-ów, każdy będzie widział główny kontekst, ale webowe konteksty nie będą widzieć siebie wzajemnie.
Warto zwrócić uwagę na to, że jeśli zdefiniujemy jakiś BeanFactoryPostProcessor albo BeanPostProcessor w głównym kontekście to NIE ZADZIAŁA dla beanów z webowego kontekstu. W dokumentacji interfejsu BeanPostProcessor czytamy :
"Plain bean factories allow for programmatic registration of post-processors, applying to all beans created through this factory."
Podobnie jest dla BeanFactoryPostProcessor. Zatem jasne.
Jedna jeszcze istotna uwaga: otóż główny kontekst jest załadowany zanim zostaną zainicjowane serwlety.
3. Zajrzyjmy do wnętrza podanych konfigów springa
- root-context.xml mam pusty - aplikacja narazie nie ma żadnej logiki :-)
- mvc-dispatcher-servlet.xml:
<context:component-scan base-package="pl.turo.spring.mvc.controller" /> <!-- 1 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 2 --> <property name="prefix"> <value>/WEB-INF/pages/</value> <!-- 3 --> </property> <property name="suffix"> <!-- 4 --> <value>.jsp</value> </property> </bean>W tym miejscu należy omówić nieco jak w teorii działa i co robi Spring MVC.
Każde żądanie które będzie obsługiwane przez DispatcherServlet przechodzi cykl:
- na podstawie żadania http należy uruchomić jakąś metodę biznesową (oczywiście przy odpowiednim wypakowaniu danych z żądania),
- metoda może coś zwrócić,
- trzeba klientowi zwrócić wynik w takiej postaci jakiej oczekuje.
Dokładnie to robi dla nas DispatcherServlet
- odbiera żadanie,
- wywołuje "odpowiednią" metodę biznesową na kontrolerze. Kontroler to nic innego jak zwykły bean. W jaki sposób serwlet szuka owej "odpowiedniej" metody pokażę niżej (konfiguracja opatrzona komentarzem "1"),
- odbiera wynik tego wywołania,
- przekierowuje renderowanie odpowiedzi do widoku. Widokiem jest klasa implementująca interfejs View. Spring dostarcza pokaźną ich liczbę i ten temat zasługuje na osobny post. DispatcherServlet odwołuje się do ViewResolver-a aby wybrać odpowiedni widok i renderować odpowiedź. Tutaj posłużyłem się klasą InternalResourceViewResolver (komentarz "2") która oczekuje że kontroler zwróci nazwę widoku. Nazwa ta jest nazwą strony jsp (komentarz "4"); szukana jest w katalogu /WEB-INF/pages (komentarz "3").
Przykład:
Jeśli kontroler zwraca "hello", to InternalResourceViewResolver oczekuje istnienia strony /WEB-INF/pages/hello.jsp i ona zostanie odesłana jako odpowiedź.
Wiele rzeczy może tu być nadal niejasnych (np jak kontroler 'rozpakowuje' parametry żądania, jak zapisuje do odpowiedzi itd...). Na ten moment nie przejmujmy się tym i idźmy dalej.
4. Prosty kontroler:
package pl.turo.spring.mvc.controller; @Controller public class HomeController { @RequestMapping("/") public String index() { return "index"; } @RequestMapping("/hello") public void sayHello(Model model) { String name = "Sławek"; model.addAttribute("imie", name); } }Widzimy dwie adnotacje:
- @Controller jest adnotacją markerową i jest podtypem @Component. Sama z siebie nic nie dodaje,
- @RequestMapping - owa adnotacja mówi dispatcher-owi że należy ją wywołać jak przyjdzie żądanie pasujące do "/hello" (przy mojej konfiguracji mapowania dispatcher-a w web.xml będzie to http://serwer:port/nazwa_aplikacji/hello). Adnotacji tej poświęcę również osobny post. Na razie niech nam wystarczy to co już wiemy.
Zapewne zauważyliście, że metoda sayHello otrzymuje w parametrze Model - jest to dekorator mapy do której możemy wkładać dane. W moim przykładzie pod kluczem "imie" trzymam zmienną name, do której odwołuję się na stronie hello.jsp:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Spring MVC by turo</title> </head> <body>Hello ${imie}</body> </html>Uruchamiam serwer, i pod adresem http://localhost:8080/mvc/hello widzę moją stronę :-). To tyle jeśli chodzi o wprowadzenie do Spring MVC. Jak zwykle udostępniam projekt; będąc bardziej na czasie niech to będzie git:
git://github.com/slturo/mvc-start.git
Brak komentarzy:
Prześlij komentarz