All MVC frameworks for web applications provide a way to address views. Spring provides view resolvers, which enable you to render models in a browser without tying you to a specific view technology. Out of the box, Spring enables you to use JSPs, Velocity templates and XSLT views, for example. See Chapter 16, View technologies for a discussion of how to integrate and use a number of disparate view technologies.
The two interfaces that are important to the way Spring handles
views are ViewResolver
and
View
. The
ViewResolver
provides a mapping between
view names and actual views. The View
interface addresses the preparation of the request and hands the request
over to one of the view technologies.
As discussed in Section 15.3, “Implementing Controllers”, all controllers
in the Spring Web MVC framework return a
ModelAndView
instance. Views in Spring are
addressed by a view name and are resolved by a view resolver. Spring
comes with quite a few view resolvers. This table lists most of them; a
couple of examples follow.
Table 15.3. View resolvers
ViewResolver | Description |
---|---|
AbstractCachingViewResolver | Abstract view resolver that caches views. Often views need preparation before they can be used; extending this view resolver provides caching. |
XmlViewResolver | Implementation of
ViewResolver that accepts a
configuration file written in XML with the same DTD as Spring's
XML bean factories. The default configuration file is
/WEB-INF/views.xml . |
ResourceBundleViewResolver | Implementation of
ViewResolver that uses bean
definitions in a ResourceBundle ,
specified by the bundle base name. Typically you define the
bundle in a properties file, located in the classpath. The
default file name is
views.properties . |
UrlBasedViewResolver | Simple implementation of the
ViewResolver interface that
effects the direct resolution of symbolic view names to URLs,
without an explicit mapping definition. This is appropriate if
your symbolic names match the names of your view resources in a
straightforward manner, without the need for arbitrary
mappings. |
InternalResourceViewResolver | Convenience subclass of
UrlBasedViewResolver that supports
InternalResourceView (in effect, Servlets
and JSPs), and subclasses such as
JstlView and
TilesView . You can specify the view class
for all views generated by this resolver by using
setViewClass(..) . See the Javadocs for the
UrlBasedViewResolver class for
details. |
VelocityViewResolver /
FreeMarkerViewResolver | Convenience subclass of
UrlBasedViewResolver that supports
VelocityView (in effect, Velocity
templates) or FreeMarkerView
,respectively, and custom subclasses of them. |
ContentNegotiatingViewResolver | Implementation of the
ViewResolver interface that
resolves a view based on the request file name or
Accept header. See Section 15.5.4, “ContentNegotiatingViewResolver”. |
As an example, with JSP as a view technology, you can use the
UrlBasedViewResolver
. This view resolver
translates a view name to a URL and hands the request over to the
RequestDispatcher to render the view.
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
When returning test
as a viewname, this view
resolver forwards the request to the
RequestDispatcher
that will send the request to
/WEB-INF/jsp/test.jsp
.
When you combine different view technologies in a web application,
you can use the
ResourceBundleViewResolver
:
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> <property name="defaultParentView" value="parentView"/> </bean>
The ResourceBundleViewResolver
inspects the
ResourceBundle
identified by the basename, and
for each view it is supposed to resolve, it uses the value of the
property [viewname].class
as the view class and the
value of the property [viewname].url
as the view url.
As you can see, you can identify a parent view, from which all views in
the properties file sort of extend. This
way you can specify a default view class, for example.
Note | |
---|---|
Subclasses of |
Spring supports multiple view resolvers. Thus you can chain
resolvers and, for example, override specific views in certain
circumstances. You chain view resolvers by adding more than one resolver
to your application context and, if necessary, by setting the
order
property to specify an order. Remember, the
higher the order property, the later the view resolver is positioned in
the chain.
In the following example, the chain of view resolvers consists of
two resolvers, a InternalResourceViewResolver
,
which is always automatically positioned as the last resolver in the
chain, and an XmlViewResolver
for specifying
Excel views. Excel views are not supported by the
InternalResourceViewResolver
.
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver"> <property name="order" value="1"/> <property name="location" value="/WEB-INF/views.xml"/> </bean> <!-- in views.xml --> <beans> <bean name="report" class="org.springframework.example.ReportExcelView"/> </beans>
If a specific view resolver does not result in a view, Spring
examines the context for other view resolvers. If additional view
resolvers exist, Spring continues to inspect them. If
they do not, it throws an Exception
.
The contract of a view resolver specifies that a view resolver
can return null to indicate the view could not be
found. Not all view resolvers do this, however, because in some cases,
the resolver simply cannot detect whether or not the view exists. For
example, the InternalResourceViewResolver
uses
the RequestDispatcher
internally, and dispatching
is the only way to figure out if a JSP exists, but this action can only
execute once. The same holds for the
VelocityViewResolver
and some others. Check the
Javadoc for the view resolver to see whether it reports non-existing
views. Thus, putting an
InternalResourceViewResolver
in the chain in a
place other than the last, results in the chain not being fully
inspected, because the
InternalResourceViewResolver
will
always return a view!
As mentioned previously, a controller typically returns a logical
view name, which a view resolver resolves to a particular view
technology. For view technologies such as JSPs that are processed
through the Servlet or JSP engine, this resolution is usually handled
through InternalResourceViewResolver
/
InternalResourceView
, which
issues an internal forward or include, through the Servlet API's
RequestDispatcher.forward(..)
or
RequestDispatcher.include()
. For other view
technologies, such as Velocity, XSLT, and so on, the view itself
produces the content on the response stream.
It is sometimes desirable to issue an HTTP redirect back to the
client, before the view is rendered. This is desirable for example when
one controller has been called with POST
ed data, and
the response is actually a delegation to another controller (for example
on a successful form submission). In this case, a normal internal
forward will mean the other controller will also see the same
POST
data, which is potentially problematic if it can
confuse it with other expected data. Another reason to do a redirect
before displaying the result is that this will eliminate the possibility
of the user doing a double submission of form data. The browser will
have sent the initial POST
, will have seen a redirect
back and done a subsequent GET
because of that, and
thus as far as it is concerned, the current page does not reflect the
result of a POST
, but rather of a
GET
, so there is no way the user can accidentally
re-POST
the same data by doing a refresh. The refresh
forces a GET
of the result page, not a resend of the
initial POST
data.
One way to force a redirect as the result of a controller
response is for the controller to create and return an instance of
Spring's RedirectView
. In this case,
DispatcherServlet
does not use the normal view
resolution mechanism. Rather because it has been given the (redirect)
view already, the DispatcherServlet
simply
instructs the view to do its work.
The RedirectView
issues an
HttpServletResponse.sendRedirect()
call, which
comes back to the client browser as an HTTP redirect. All
model attributes are exposed as HTTP query parameters. This does mean
that the model must contain only objects (generally Strings or
convertible to Strings), which can be readily converted to a
string-form HTTP query parameter.
If you use RedirectView
and the view is
created by the controller itself, it is recommended that you configure
the redirect URL to be injected into the controller so that it is not
baked into the controller but configured in the context along with the
view names. The
next section discusses this process.
While the use of RedirectView
works fine,
if the controller itself is creating the
RedirectView
, there is no getting around the
fact that the controller is aware that a redirection is happening.
This is really suboptimal and couples things too tightly. The
controller should not really care about how the response gets
handled. In general it should operate only in terms of view names that
have been injected into it.
The special redirect:
prefix allows you to
accomplish this. If a view name is returned that has the prefix
redirect:, then UrlBasedViewResolver
(and all
subclasses) recognize this as a special indication that a redirect is
needed. The rest of the view name will be treated as the redirect
URL.
The net effect is the same as if the controller had returned a
RedirectView
, but now the controller itself can
simply operate in terms of logical view names. A logical view name
such as redirect:/my/response/controller.html
will
redirect relative to the current servlet context, while a name such as
redirect:http://myhost.com/some/arbitrary/path.html
will redirect to an absolute URL. The important thing is that as long
as this redirect view name is injected into the controller like any
other logical view name, the controller is not even aware that
redirection is happening.
It is also possible to use a special forward:
prefix for view names that are ultimately resolved by
UrlBasedViewResolver
and subclasses. All this
does is create an InternalResourceView
(which
ultimately does a RequestDispatcher.forward()
)
around the rest of the view name, which is considered a URL.
Therefore, this prefix is not useful with
InternalResourceViewResolver
/
InternalResourceView
(for JSPs for example).
But the prefix can be helpful when you are primarily using another
view technology, but still want to force a forward of a resource to be
handled by the Servlet/JSP engine. (Note that you may also chain
multiple view resolvers, instead.)
As with the redirect:
prefix, if the view
name with the prefix is just injected into the controller, the
controller does not detect that anything special is happening in terms
of handling the response.
The ContentNegotiatingViewResolver
does not
resolve views itself, but rather delegates to other view resolvers,
selecting the view that resembles the representation requested by the
client. Two strategies exist for a client to request a representation
from the server:
Use a distinct URI for each resource,
typically by using a different file extension in the URI. For
example, the URI
http://www.example.com/users/fred.pdf
requests a PDF
representation of the user fred, and
http://www.example.com/users/fred.xml
requests an
XML representation.
Use the same URI for the client to locate the resource, but
set the Accept
HTTP request header to list the
media
types that it understands. For example, an HTTP request for
http://www.example.com/users/fred
with an
Accept
header set to application/pdf
requests a PDF representation of the user fred while
http://www.example.com/users/fred
with an
Accept
header set to text/xml
requests an XML representation. This strategy is known as content
negotiation.
Note | |
---|---|
One issue with the Accept header is that is impossible to change it in a web browser, in HTML. For example, in Firefox, it is fixed to Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 For this reason it is common to see the use of a distinct URI for each representation. |
To support multiple representations of a resource, Spring provides
the ContentNegotiatingViewResolver
to resolve a
view based on the file extension or Accept
header of
the HTTP request. ContentNegotiatingViewResolver
does not perform the view resolution itself, but instead delegates to a
list of view resolvers that you specify through the bean property
ViewResolvers
.
The ContentNegotiatingViewResolver
selects
an appropriate View
to handle the request by
comparing the request media type(s) with the media type (also known as
Content-Type
) supported by the
View
associated with each of its
ViewResolvers
. The first
View
in the list that has a compatible
Content-Type
returns the representation to the
client. The Accept
header may include wildcards, for
example text/*, in which case a View
whose
Context-Type was text/xml is a compatible match.
To support the resolution of a view based on a file extension, you
use the ContentNegotiatingViewResolver
bean
property MediaTypes
to specify a mapping of file
extensions to media types. For more information on the algorithm to
determine the request media type, refer to the API documentation for
ContentNegotiatingViewResolver
.
Here is an example configuration of a
ContentNegotiatingViewResolver:
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="mediaTypes"> <map> <entry key="atom" value="application/atom+xml"/> <entry key="html" value="text/html"/> </map> </property> <property name="viewResolvers"> <list> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </list> </property> </bean> <bean id="content" class="com.springsource.samples.rest.SampleContentAtomView"/>
The InternalResourceViewResolver
handles
the translation of view names and JSP pages while the
BeanNameViewResolver
returns a view based on the
name of a bean. (See "Resolving views with the
ViewResolver interface" for more details on how Spring looks up
and instantiates a view.) In this example, the
content
bean is a class that inherits from
AbstractAtomFeedView
, which returns an Atom RSS
feed. For more information on creating an Atom Feed representation, see
the section Atom Views.
In this configuration, if a request is made with an .html
extension, the view resolver looks for a view that matches the text/html
media type. The InternalResourceViewResolver
provides the matching view for text/html. If the request is made with
the file extension .atom, the view resolver looks for a view that
matches the application/atom+xml media type. This view is provided by
the BeanNameViewResolver
that maps to the
SampleContentAtomView
if the view name returned
is content
. Alternatively, client requests can be
made without a file extension and setting the Accept header to the
preferred media-type and the same resolution of request to views would
occur.
Note | |
---|---|
If |
The corresponding controller code that returns an Atom RSS feed
for a URI of the form http://localhost/content.atom
or http://localhost/content
with an
Accept
header of application/atom+xml is shown
below
@Controller public class ContentController { private List<SampleContent> contentList = new ArrayList<SampleContent>(); @RequestMapping(value="/content", method=RequestMethod.GET) public ModelAndView getContent() { ModelAndView mav = new ModelAndView(); mav.setViewName("content"); mav.addObject("sampleContentList", contentList); return mav; } }