środa, 7 listopada 2012

Spring MVC - ViewResolver-s

Dzisiaj omówię bliżej temat ViewRosolver-ów w Spring MVC. W poprzednim poście wspomniałem, że DispatcherSevlet korzysta z ViewResolvera aby wyrenderować odpowiedź (View). Kluczową sprawą jest zrozumienie w jaki sposób kontroler zwraca nazwę widoku do dispatcher-a.
Można to zrobić na kilka sposobów:
- nazwa na podstawie url-a:
@RequestMapping("/hello")
public void sayHello() {
...
}
szukaną nazwą widoku będzie "hello" - przez konwencję jeśli kontroler nie zwraca widoku (bądź jego nazwy) to nazwą widoku staje się url.


- nazwa bezpośrednio zwrócona:
@RequestMapping("/anotherHello")
public String anotherHello() {
    return "hello";
}
- utworzenie ModelAndView-a
@RequestMapping("/modelAndViewHello")
public ModelAndView modelAndViewHello() {
    return new ModelAndView("hello");
}
- ModelAndView z parametru wywołania
@RequestMapping("/parameterModelAndViewHello")
public ModelAndView parameterModelAndViewHello(ModelAndView view) {
    view.setViewName("hello");
    return view;
}
Jak widać możemy sami stworzyć obiekt ModelAndView, ale równie dobrze spring może nam go przekazać wywołując metodę kontrolera (gdzie widok i model będą null).

Wiemy już jak kontroler zwraca nam nazwę widoku, przejdźmy do tego jak resolver-y zwracają obiekty reprezentujące widok. Przyjrzyjmy się niektórym dostarczonym przez springa:


<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
        <value>/WEB-INF/pages/</value> <!-- 1 -->
    </property>
    <property name="suffix">
        <value>.jsp</value>            <!-- 2 -->
    </property>
</bean>
InternalResourceViewResolver na podstawie nazwy zwraca odpowiedni widok - obiekt klasy implementującej interfejs View. Dla InternalResourceViewResolver-a domyślnie jest to JstlView. Możemy to zmienić atrybutem "viewClass". Kiedy DispatcherServlet pyta o widok dla danej nazwy, to refleksyjnie zostaną powołane instancje, zostaną dynamicznie dodane do fabryki springa i staną się bean-ami. Nazwą tego beana będzie nazwa widoku. Można się o tym łatwo przekonać tworząc bean-a takej samej jak jedna z nazw widoków, np:
<bean id="hello" class="java.lang.String">
    <constructor-arg value="check if view is really spring bean"/>
</bean>
Przy próbie odwołania się do tego widoku otrzymamy "org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'hello' must be of type [org.springframework.web.servlet.View], but was actually of type [java.lang.String]". Ciekawą tego konsekwencją jest też to, że możemy sobie sami utworzyć widok będący implementacją org.springframework.web.servlet.View. Skoro jest to bean springa to prawdziwe jest dla niego wszystko, tak jak dla zwykłego beana (na przykład AOP). InternalResourceView (a więc też JstlView) wymaga podania URL-a, do którego się odnosi. URL zbudowany zostanie w sposób następujący: prefix + nazwa_widoku_zwracana_przez_kontroler + postfix. Czyli dla wyżej podanego przykładu z metodą sayHello będzie to : " /WEB-INF/pages/hello.jsp".

Podsumowując InternalResourceViewResolver na podstawie swojej konfiguracji szuka widoku.  Należy dodać, że tworzy widok nie sprawdzając czy istnieje strona odpowiadająca temu widokowi. Przykład:
@RequestMapping("/notExistView")
public void parameterModelAndViewHello() {}
InternalResourceViewResolver stworzy bean-a o nazwie "notExistView" i url="/WEB-INF/pages/notExistView.jsp". Strona nie istnieje, więc otrzymamy 404.

  • ResourceBundleViewResolver. Jeśli chcemy mieć różne klasy widoków możemy skorzystać z tego resovera. ResourceBundleViewResolver wymaga podania pliku properties mapującego nazwę widoku zwróconego przez kontroler na typ (implementację) widoku. Przykład:
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="resource-bundle-views" />
    <property name="order" value="0" />
</bean>
Atrybut basename określa nazwę pliku properties (można nie podać tego atrybutu, domyślna nazwa to "views"). Atrybut order określa kolejność w jakiej DispatcherServlet będzie się odpytywał ViewResolver-ów o widoki. Jeśli ResourceBundleViewResolver nie zwróci widoku, to spyta się InternalResourceViewResolver-a, który jak już wiemy obiekt widoku zawsze zwróci. Mój plik znajduje się w mvc-views/src/main/resources/resource-bundle-views.properties i ma zawartość:
anotherHello.(class)=org.springframework.web.servlet.view.JstlView
anotherHello.url=WEB-INF/pages/anotherHello.jsp
Powyższą konfigurację należy czytać następująco: uzyj obiektu typu JstlView dla widoku o nazwie anotherHello i niech ten widok odnosi się do zasobu WEB-INF/pages/anotherHello.jsp.


  • Spring posiada oczywiście więcej ViewResolver-ów, których jednak nie będę szczegółowo opisywał. 
Udostępniam projekt na githubie: git://github.com/slturo/view-resolvers.git. W następnym poście opiszę nieco szerzej różne widoki.

Brak komentarzy:

Prześlij komentarz