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. The section entitled Chapter 17, View technologies has details of how to integrate and use a number of disparate view technologies.
The two interfaces which 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 the section entitled Section 16.3, “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. We'll list most
of them and then provide a couple of examples.
Table 16.4. View resolvers
ViewResolver | Description |
---|---|
AbstractCachingViewResolver | An abstract view resolver which takes care of caching views. Often views need preparation before they can be used, extending this view resolver provides caching of views. |
XmlViewResolver | An 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 | An implementation of
ViewResolver that uses bean
definitions in a ResourceBundle ,
specified by the bundle basename. The bundle is typically
defined in a properties file, located in the classpath. The
default file name is
views.properties . |
UrlBasedViewResolver | A 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 | A convenience subclass of
UrlBasedViewResolver that supports
InternalResourceView (i.e. Servlets and
JSPs), and subclasses such as JstlView
and TilesView . The view class for all
views generated by this resolver can be specified via
setViewClass(..) . See the Javadocs for the
UrlBasedViewResolver class for
details. |
VelocityViewResolver /
FreeMarkerViewResolver | A convenience subclass of
UrlBasedViewResolver that supports
VelocityView (i.e. Velocity templates) or
FreeMarkerView respectively and custom
subclasses of them. |
As an example, when using JSP for 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 will hand the request over to the
RequestDispatcher
that will send the request to
/WEB-INF/jsp/test.jsp
.
When mixing 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.
A note on caching - subclasses of
AbstractCachingViewResolver
cache view instances
they have resolved. This greatly improves performance when using certain
view technologies. It's possible to turn off the cache, by setting the
cache
property to false
.
Furthermore, if you have the requirement to be able to refresh a certain
view at runtime (for example when a Velocity template has been
modified), you can use the removeFromCache(String viewName,
Locale loc)
method.
Spring supports more than just one view resolver. This allows you
to chain resolvers and, for example, override specific views in certain
circumstances. Chaining view resolvers is pretty straightforward - just
add more than one resolver to your application context and, if
necessary, set the order
property to specify an
order. Remember, the higher the order property, the later the view
resolver will be 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 (which 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 will
inspect the context to see if other view resolvers are configured. If
there are additional view resolvers, it will continue to inspect them.
If not, it will throw an Exception
.
You have to keep something else in mind - the contract of a view
resolver mentions that a view resolver can return
null to indicate the view could not be found. Not all view resolvers do
this however! This is 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 - this can only be done once.
The same holds for the VelocityViewResolver
and
some others. Check the Javadoc for the view resolver to see if you're
dealing with a view resolver that does not report non-existing views. As
a result of this, putting an
InternalResourceViewResolver
in the chain in a
place other than the last, will result in the chain not being fully
inspected, since the InternalResourceViewResolver
will always return a view!
As has been mentioned, a controller normally returns a logical
view name, which a view resolver resolves to a particular view
technology. For view technologies such as JSPs that are actually
processed via the Servlet/JSP engine, this is normally handled via
InternalResourceViewResolver
/
InternalResourceView
which will ultimately end up
issuing an internal forward or include, via the Servlet API's
RequestDispatcher.forward(..)
or
RequestDispatcher.include()
. For other view
technologies, such as Velocity, XSLT, etc., 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
would just force 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
will not use the normal view
resolution mechanism, but rather as it has been given the (redirect)
view already, will just ask it to do its work.
The RedirectView
simply ends up issuing
an HttpServletResponse.sendRedirect()
call, which
will come back to the client browser as an HTTP redirect. All model
attributes are simply 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 using RedirectView
and the view is
created by the controller itself, it is preferable for 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.
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... it should generally think only in terms of view names that
have been injected into it.
The special redirect:
prefix allows this to
be achieved. If a view name is returned which has the prefix
redirect:, then UrlBasedViewResolver
(and all
subclasses) will 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
deal just 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 will ultimately be 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, there is never any use in using this prefix when using
InternalResourceViewResolver
/
InternalResourceView
anyway (for JSPs for
example), but it's of potential use when you are primarily using
another view technology, but still want to force a forward to happen
to 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 have to be aware that anything special is
happening in terms of handling the response.