Spring's MVC framework is built around the concept of a handler (controller) returning org.springframework.web.servlet.ModelAndView objects. The ModelAndView is named after what it contains, and with the model, the view should be able to render an appropriate response sent back to the client.
The model is a map containing keys (Strings) mapped to values representing the actual value of the entry. While rendering the view, the map will be available specific to the view technology used (for example, in the VelocityContext when using Velocity or in the request context when using JSPs).
The handling of a request should result in a ModelAndView object being returned. The DispatcherServlet knows how to use ModelAndView object and in the end needs a concrete org.springframework.web.servlet.View instance that will be responsible for rendering the view: for example, a JavaServer Page or a Velocity template. Construction of a ModelAndView object is possible using either the concrete view object or a logical name. To get from a logical view name to a view, an org.springframework.web.servlet.view.ViewResolver is used. ViewResolvers manage a mapping between logical view names and their concrete View object counterparts. Different implementations of the ViewResolver interface have different behavior when looking at how the mapping is actually being done.
A ViewResolver resembles all other web-related components in that it also needs to be declared in the context of your web application and will be automatically picked up by Spring. Let's have a look at some of the different ViewResolvers Spring provides.
The org.springframework.web.servlet.view.UrlBasedViewResolver maps logical view names directly to URLs that it hands over to the view class specified. The view class in turn uses the URL to render the response. The URL can, for instance, point to a JavaServer Page, a Velocity template, or an XSLT style sheet. You can configure the UrlBasedViewResolver in a couple of ways. First of all you need to specify a view class. The view class will eventually render your view (JSP, Velocity template, and so on). You can also tweak the way the URLs are created, using a prefix and a suffix.
The UrlBasedViewResolver comes in two shapes: InternalResourceViewResolver and the VelocityViewResolver, which have only minor differences between them. The InternalResourceViewResolver is a convenient resolver, already setting a specific view class (InternalResourceView), but still allowing you to override it. The VelocityViewResolver applies some Velocity-specific properties to all views it creates, also allowing you to override those. They include the NumberTool and DateTool objects, which allow for easy formatting of numbers and dates. Velocity features will be more extensively covered in the next chapter, when we discuss the integration of different view technologies.
<bean > <property name="viewClass"> <value>org.springframework.web.servlet.view.JstlView</value> </property> <property name="prefix"><value>/WEB-INF/jsps</value></property> <property name="suffix"><value>.jsp</value></property> </bean>
This will result in a logical view name welcome being mapped to /WEB-INF/jsps/welcome.jsp. The JstlView class specified will result in a special Java Standard Tag Library object being merged into the request context, but again, this will be covered later in this chapter.
The org.springframework.web.servlet.view.BeanNameViewResolver and the org.springframework.web.servlet.view.XmlViewResolver both provide functionality to retrieve views from a BeanFactory. The latter uses a BeanFactory dedicated to views, which, once loaded, is not visible to the rest of the application. The BeanNameViewResolver queries only the BeanFactory in which it was declared.
<bean > <property name="url"><value>/WEB-INF/jsps/welcome.jsp</value></property> </bean> <bean />
Suppose you're using the configuration as specified. After returning welcome as the logical view name, the BeanNameViewResolver will retrieve the view bean from the context and use this to render the JSP.
As briefly mentioned in the preceding text, the XmlViewResolver basically has the same behavior with one difference: a BeanFactory dedicated to holding the views is instantiated and inspected when a view is needed. This means you will have to declare your beans in a separate file, for example /WEBINF/views/views.xml, and wire up the XmlViewResolver as follows:
<bean > <property name="location"><value>/WEB-INF/views/views.xml</value></property> </bean>
The behavior of the org.springframework.web.servlet.view.ResourceBundleViewResolver resembles that of the XmlViewResolver, but when using the former, resource bundles containing bean definitions are used instead of XML files. The ResourceBundleViewResolver allows for localized views based on a locale from the user's request.
# /WEB-INF/classes/views.properties index.class=org.springframework.web.servlet.view.JstlView index.url=/WEB-INF/jsps/index.jsp welcome.url=/WEB-INF/jsps/welcome.jsp store.url=/WEB-INF/jsps/store.jsp # /WEB-INF/classes/views_nl.properties welcome.url=/WEB-INF/jsps/nl/welcome.jsp <bean > <property name="baseName"><value>views</value></property> <property name="defaultParentView"><value>index</value></property> </bean>
The preceding code sample shows two Properties files where one is the default containing three views and the second one overrides one view for a specific language. Note that it's better not to provide separate JSPs to do localization but instead localize the JSPs themselves, using the JSTL localization tags or the Spring message tag (more about that later). Just to show the behavior of the ResourceBundleViewResolver, we did it here anyway.
Upon startup, the ViewResolver will load the resource bundles, using the basename specified with the bean definition. The view will be resolved based on the locale associated with a request. Also defined with the bean definition of the view resolver is a defaultParentView. Because it would be annoying to specify the view class with every view you're defining, the default parent view will be used for any missing values (such as the view class). Overriding is of course always possible.
Spring integrates with a variety of view technologies, some of which need a dedicated ViewResolver. The next chapter provides more insight into those view resolvers.
While one view resolver will suffice in most cases, Spring offers the possibility to chain view resolvers. Chaining of view resolvers is ideal in situations such as those where you want to have fallbacks, if you want to provide support for different view technologies, or if you have stored your templates in different locations.
Spring's contract for chaining of view resolvers is that if a view resolver returns null from the resolveViewName() method it starts looking for other view resolvers to allow those to try to resolve the view.
Before we show you an example of how to chain view resolvers, you should know that some view resolvers always attempt to resolve the view name and are not aware of whether or not the underlying resource exists. This is the case for all view resolvers that hand over control to an engine or other mechanism that doesn't inform the view resolver in case a resource does not exist. Specifically, this is the case for all UrlBasedViewResolvers: the InternalResourceViewResolver, the FreeMarkerViewResolver, and the VelocityViewResolver. As a result of this, these view resolvers must always be last in the chain.
The following example shows how to use two view resolvers: one based on bean names and one based on internal resources. This example shows how to override just one view. In this case, we want to present results of one specific request in an Excel file instead of an HTML page generated by a JSP.
<bean > <property name="order"><value>1</value></property> </bean> <bean > <property name="order"><value>2</value></property> <property name="prefix"><value>/WEB-INF/jsp/</value></property> <property name="suffix"><value>.jsp</value></property> <property name="viewClass"> <value>org.springframework.web.servlet.view.JstlView</value> </property> </bean> <bean name="listShows" />
We have to specify an order because the DispatcherServlet does not know in which order we want to have our view resolvers questioned. The DispatcherServlet detects all view resolvers, sorts them based on the org.springframework.core.Ordered interface (see Chapter 3), and inspects the one with the lowest order integer first. In this case, it's the BeanNameViewResolver, which causes an Excel view to be rendered if the view name is listShows, and a JSP to be rendered otherwise.
We've run into situations where users developed a controller that also implemented the ViewResolver interface. While it's questionable why that should be necessary, we've included a facility to disable view resolver chaining to support it. Chaining does not have an impact on performance, so if you don't need to disable it, you can leave the setting as is.
Enter the following in your web.xml file if you want to disable view resolver chaining:
<servlet> <servlet-name>chaining</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>detectAllViewResolvers</param-name> <param-value>false</param-value> </init-param> </servlet>
You can also disable the detection of multiple HandlerMappings and multiple HandlerExceptionResolvers if you need to. The same principles apply with only the initialization parameter differing. For exception resolvers, set the detectAllHandlerExceptionResolvers property to false. For HandlerMappings, do the same with the detectAllViewResolvers property. When you turn off the detection of multiple components of the same type, you are required to specify the bean name to tell Spring which of the bean definitions represents the one you wish to use. This is the common bean name, mentioned in the listing of all Spring MVC component types earlier in this chapter.
Again, in most situations you shouldn't need to change the default setting, which is to detect all components of the same type available. Spring MVC's flexibility doesn't mean that you always need to use all the choices offered.