Spring's web MVC framework is designed around a DispatcherServlet that dispatches requests to handlers, with configurable handler mappings, view resolution, locale and theme resolution as well as support for upload files. The default handler is a very simple Controller interface, just offering a ModelAndView handleRequest(request,response) method. This can already be used for application controllers, but you will prefer the included implementation hierarchy, consisting of, for example AbstractController, AbstractCommandController and SimpleFormController. Application controllers will typically be subclasses of those. Note that you can choose an appropriate base class: If you don't have a form, you don't need a FormController. This is a major difference to Struts.
You can use any object as a command or form object - there's no need to implement an interface or derive from a base class. Spring's data binding is highly flexible, for example, it treats type mismatches as validation errors that can be evaluated by the application, not as system errors. So you don't need to duplicate your business objects' properties as Strings in your form objects, just to be able to handle invalid submissions, or to convert the Strings properly. Instead, it is often preferable to bind directly to your business objects. This is another major difference to Struts which is built around required base classes like Action and ActionForm - for every type of action.
Compared to WebWork, Spring has more differentiated object roles. It supports the notion of a Controller, an optional command or form object, and a model that gets passed to the view. The model will normally include the command or form object but also arbitrary reference data. Instead, a WebWork Action combines all those roles into one single object. WebWork does allow you to use existing business objects as part of your form, but only by making them bean properties of the respective Action class. Finally, the same Action instance that handles the request is used for evaluation and form population in the view. Thus, reference data needs to be modeled as bean properties of the Action too. These are arguably too many roles for one object.
Spring's view resolution is extremely flexible. A Controller implementation can even write a view directly to the response, returning null as ModelAndView. In the normal case, a ModelAndView instance consists of a view name and a model Map, containing bean names and corresponding objects (like a command or form, containing reference data). View name resolution is highly configurable, either via bean names, via a properties file, or via your own ViewResolver implementation. The abstract model Map allows for complete abstraction of the view technology, without any hassle. Any renderer can be integrated directly, whether JSP, Velocity, or any other rendering technology. The model Map is simply transformed into an appropriate format, such as JSP request attributes or a Velocity template model.
There are several reasons why some projects will prefer to use other MVC implementations. Many teams expect to leverage their existing investment in skills and tools. In addition, there is a large body of knowledge and experience avalailable for the Struts framework. Thus, if you can live with Struts' architectural flaws, it can still be a viable choice for the web layer. The same applies to WebWork and other web MVC frameworks.
If you don't want to use Spring's web MVC, but intend to leverage other solutions that Spring offers, you can integrate the web MVC framework of your choice with Spring easily. Simply start up a Spring root application context via its ContextLoaderListener, and access it via its ServletContext attribute (or Spring's respective helper method) from within a Struts or WebWork action. Note that there aren't any "plugins" involved, so no dedicated integration is necessary. From the web layer's point of view, you'll simply use Spring as a library, with the root application context instance as the entry point.
All your registered beans and all of Spring's services can be at your fingertips even without Spring's web MVC. Spring doesn't compete with Struts or WebWork in this scenario, it just addresses the many areas that the pure web MVC frameworks don't, from bean configuration to data access and transaction handling. So you are able to enrich your application with a Spring middle tier and/or data access tier, even if you just want to use, for example, the transaction abstraction with JDBC or Hibernate.
Spring's web module provides a wealth of unique web support features, including:
Clear separation of roles - controller, validator, command object, form object, model object, DispatcherServlet, handler mapping, view resolver, etc. Each role can be fulfilled by a specialized object.
Powerful and straightforward configuration of both framework and application classes as JavaBeans, including easy referencing across contexts, such as from web controllers to business objects and validators.
Adaptability, non-intrusiveness. Use whatever controller subclass you need (plain, command, form, wizard, multi-action, or a custom one) for a given scenario instead of deriving from a single controller for everything.
Reusable business code - no need for duplication. You can use existing business objects as command or form objects instead of mirroring them in order to extend a particular framework base class.
Customizable binding and validation - type mismatches as application-level validation errors that keep the offending value, localized date and number binding, etc instead of String-only form objects with manual parsing and conversion to business objects.
Customizable handler mapping and view resolution - handler mapping and view resolution strategies range from simple URL-based configuration, to sophisticated, purpose-built resolution strategies. This is more flexible than some web MVC frameworks which mandate a particular technique.
Flexible model transfer - model transfer via a name/value Map supports easy integration with any view technology.
Customizable locale and theme resolution, support for JSPs with or without Spring tag library, support for JSTL, support for Velocity without the need for extra bridges, etc.
A simple but powerful tag library that avoids HTML generation at any cost, allowing for maximum flexibility in terms of markup code.
Spring's web MVC framework is, like many other web MVC frameworks, a request-driven web MVC framework, designed around a servlet that dispatches requests to controllers and offers other functionality facilitating the development of web applications. Spring's DispatcherServlet however, does more than just that. It is completely integrated with the Spring ApplicationContext and allows you to use every other feature Spring has.
Like ordinary servlets, the DispatcherServlet is declared in the web.xml of your web application. Requests that you want the DispatcherServlet to handle, will have to be mapped, using a URL mapping in the same web.xml file.
<web-app> ... <servlet> <servlet-name>example</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>example</servlet-name> <url-pattern>*.form</url-pattern> </servlet-mapping> </web-app>
In the example above, all requests ending with .form will be handled by the DispatcherServlet. The DispatcherServlet now needs to be configured.
As illustrated in Section 3.11, “Introduction to the ApplicationContext”, ApplicationContexts in Spring can be scoped. In the web MVC framework, each DispatcherServlet has its own WebApplicationContext, which inherits all the beans already defined in in the Root WebApplicationContext. These inherited beans defined can be overridden in the servlet-specific scope, and new scope-specific beans can be defined local to a given servlet instance.
The framework will, on initialization of a DispatcherServlet, look for a file named [servlet-name]-servlet.xml in the WEB-INF directory of your web application and create the beans defined there (overriding the definitions of any beans defined with the same name in the global scope).
The config location used by the DispatcherServlet can be modified through a servlet initialization parameter (see below for details).
The WebApplicationContext is just an ordinary ApplicationContext that has some extra features necessary for web applications. It differs from a normal ApplicationContext in that it is capable of resolving themes (see Section 13.7, “Using themes”), and that is knows which servlet it is associated with (by having a link to the ServletContext). The WebApplicationContext is bound in the ServletContext, and using RequestContextUtils you can always lookup the WebApplicationContext in case you need it.
The Spring DispatcherServlet has a couple of special beans it uses, in order to be able to process requests and render the appropriate views. These beans are included in the Spring framework and can be configured in the WebApplicationContext, just as any other bean would be configured. Each of those beans, is described in more detail below. Right now, we'll just mention them, just to let you know they exist and to enable us to go on talking about the DispatcherServlet. For most of the beans, defaults are provided so you don't have to worry about configuring them.
Table 13.1. Special beans in the WebApplicationContext
Expression | Explanation |
---|---|
handler mapping(s) | (Section 13.4, “Handler mappings”) a list of pre- and postprocessors and controllers that will be executed if they match certain criteria (for instance a matching URL specified with the controller) |
controller(s) | (Section 13.3, “Controllers”) the beans providing the actual functionality (or at least, access to the functionality) as part of the MVC triad |
view resolver | (Section 13.5, “Views and resolving them”) capable of resolving view names to views, used by the DispatcherServlet |
locale resolver | (Section 13.6, “Using locales”) capable of resolving the locale a client is using, in order to be able to offer internationalized views |
theme resolver | (Section 13.7, “Using themes”) capable of resolving themes your web application can use, for example, to offer personalized layouts |
multipart resolver | (Section 13.8, “Spring's multipart (fileupload) support”) offers functionality to process file uploads from HTML forms |
handlerexception resolver | (Section 13.9, “Handling exceptions”) offers functionality to map exceptions to views or implement other more complex exception handling code |
When a DispatcherServlet is setup for use and a request comes in for that specific DispatcherServlet it starts processing it. The list below describes the complete process a request goes through if handled by a DispatcherServlet:
The WebApplicationContext is searched for and bound in the request as an attribute in order for the controller and other elements in the process to use. It is bound by default under the key DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE.
The locale resolver is bound to the request to let elements in the process resolve the locale to use when processing the request (rendering the view, preparing data, etc.) If you don't use the resolver, it won't affect anything, so if you don't need locale resolving, you don't have to use it.
The theme resolver is bound to the request to let elements such as views determine which theme to use. The theme resolver does not affect anything if you don't use it, so if you don't need themes you can just ignore it.
If a multipart resolver is specified, the request is inspected for multiparts and if they are found, it is wrapped in a MultipartHttpServletRequest for further processing by other elements in the process. (See Section 13.8.2, “Using the MultipartResolver” for further information about multipart handling).
An appropriate handler is searched for. If a handler is found, the execution chain associated with the handler (preprocessors, postprocessors, controllers) will be executed in order to prepare a model.
If a model is returned, the view is rendered, using the view resolver that has been configured with the WebApplicationContext. If no model is returned (which could be due to a pre- or postprocessor intercepting the request, for example, for security reasons), no view is rendered, since the request could already have been fulfilled.
Exceptions that might be thrown during processing of the request get picked up by any of the handlerexception resolvers that are declared in the WebApplicationContext. Using these exception resolvers you can define custom behavior in case such exceptions get thrown.
The Spring DispatcherServlet also has support for returning the last-modification-date, as specified by the Servlet API. The process of determining the last modification date for a specific request, is simple. The DispatcherServlet will first lookup an appropriate handler mapping and test if the handler that is found implements the interface LastModified and if so, the value of long getLastModified(request) is returned to the client.
You can customize Spring's DispatcherServlet by adding context parameters in the web.xml file or servlet init parameters. The possibilities are listed below.
Table 13.2. DispatcherServlet initialization parameters
Parameter | Explanation |
---|---|
contextClass | Class that implements WebApplicationContext, which will be used to instantiate the context used by this servlet. If this parameter isn't specified, the XmlWebApplicationContext will be used. |
contextConfigLocation | String which is passed to the context instance (specified by contextClass) to indicate where context(s) can be found. The String is potentially split up into multiple strings (using a comma as a delimiter) to support multiple contexts (in case of multiple context locations, of beans that are defined twice, the latest takes precedence). |
namespace | the namespace of the WebApplicationContext. Defaults to [server-name]-servlet. |
The notion of a controller is part of the MVC design pattern. Controllers define application behavior, or at least provide access to the application behavior. Controllers interpret user input and transform the user input into a sensible model which will be represented to the user by the view. Spring has implemented the notion of a controller in a very abstract way enabling a wide variety of different kinds of controllers to be created. Spring contains formcontroller, commandcontroller, controllers that execute wizard-style logic, and more.
Spring's basis for the controller architecture is the org.springframework.web.servlet.mvc.Controller interface, which is listed below.
public interface Controller { /** * Process the request and return a ModelAndView object which the DispatcherServlet * will render. */ ModelAndView handleRequest( HttpServletRequest request, HttpServletResponse response) throws Exception; }
As you can see, the Controller interface requires a single method that should be capable of handling a request and returning an appropriate model and view. These three concepts are the basis for the Spring MVC implementation - ModelAndView and Controller. While the Controller interface is quite abstract, Spring offers a lot of controllers that already contain a lot of the functionality you might need. The Controller interface just defines the most common functionality required of every controller - handling a request and returning a model and a view.
Of course, just a controller interface isn't enough. To provide a basic infrastructure, all of Spring's Controllers inherit from AbstractController, a class offering caching support and, for example, the setting of the mimetype.
Table 13.3. Features offered by the AbstractController
Feature | Explanation |
---|---|
supportedMethods | indicates what methods this controller should accept. Usually this is set to both GET and POST, but you can modify this to reflect the method you want to support. If a request is received with a method that is not supported by the controller, the client will be informed of this (using a ServletException)). |
requiresSession | indicates whether or not this controller requires a session to do its work. This feature is offered to all controllers. If a session is not present when such a controller receives a request, the user is informed using a ServletException. |
synchronizeSession | use this if you want handling by this controller to be synchronized on the user's session. To be more specific, extending controller will override the handleRequestInternal method, which will be synchronized if you specify this variable. |
cacheSeconds | when you want a controller to generate a caching directive in the HTTP response, specify a positive integer here. By default it is set to -1 so no caching directives will be included. |
useExpiresHeader | tweaks your controllers to specify the HTTP 1.0 compatible "Expires" header. By default it's set to true, so you won't have to change it. |
useCacheHeader | tweaks your controllers to specify the HTTP 1.1 compatible "Cache-Control" header. By default this is set to true so you won't have to change it. |
The last two properties are actually part of the WebContentGenerator which is the superclass of AbstractController but are included here for completeness.
When using the AbstractController as a baseclass for your controllers (which is not recommended since there are a lot of other controllers that might already do the job for you) you only have to override the handleRequestInternal(HttpServletRequest, HttpServletResponse) method, implement your logic, and return a ModelAndView object. Here is short example consisting of a class and a declaration in the web application context.
package samples; public class SampleController extends AbstractController { public ModelAndView handleRequestInternal( HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mav = new ModelAndView("foo"); mav.addObject("message", "Hello World!"); return mav; } }
<bean id="sampleController" class="samples.SampleController"> <property name="cacheSeconds" value="120"/> </bean>
The class above and the declaration in the web application context is all you need besides setting up a handler mapping (see Section 13.4, “Handler mappings”) to get this very simple controller working. This controller will generate caching directives telling the client to cache things for 2 minutes before rechecking. This controller returns an hard-coded view (hmm, not so nice), named index (see Section 13.5, “Views and resolving them” for more information about views).
Although you can extend AbstractController, Spring provides a number of concrete implementations which offer functionality that is commonly used in simple MVC applications. The ParameterizableViewController is basically the same as the example above, except for the fact that you can specify the view name that it will return in the web application context (ahhh, no need to hard-code the viewname).
The UrlFilenameViewController inspects the URL and retrieves the filename of the file request (the filename of http://www.springframework.org/index.html is index) and uses that as a viewname. Nothing more to it.
Spring offers a multi-action controller with which you aggregate multiple actions into one controller, grouping functionality together. The multi-action controller lives in a separate package - org.springframework.web.servlet.mvc.multiaction - and is capable of mapping requests to method names and then invoking the right method name. Using the multi-action controller is especially handy when you have a lot of common functionality in one controller, but want to have multiple entry points to the controller, for example, to tweak behavior.
Table 13.4. Features offered by the MultiActionController
Feature | Explanation |
---|---|
delegate | there are two usage-scenarios for the MultiActionController. Either you subclass the MultiActionController and specify the methods that will be resolved by the MethodNameResolver on the subclass (in which case you don't need to set the delegate), or you define a delegate object, on which methods resolved by the Resolver will be invoked. If you choose this scenario, you will have to define the delegate using this configuration parameter as a collaborator. |
methodNameResolver | somehow the MultiActionController will need to resolve the method it has to invoke, based on the request that came in. You can define a resolver that is capable of doing that using this configuration parameter. |
Methods defined for a multi-action controller need to conform to the following signature:
// actionName can be replaced by any methodname ModelAndView actionName(HttpServletRequest, HttpServletResponse);
Method overloading is not allowed since it would confuse the MultiActionController. Furthermore, you can define exception handlers capable of handling exceptions that are thrown by the methods you specify. Exception handler methods need to return a ModelAndView object, just as any other action method and need to conform to the following signature:
// anyMeaningfulName can be replaced by any methodname ModelAndView anyMeaningfulName(HttpServletRequest, HttpServletResponse, ExceptionClass);
The ExceptionClass can be any exception, as long as it's a subclass of java.lang.Exception or java.lang.RuntimeException.
The MethodNameResolver is supposed to resolve method names based on the request coming in. There are three resolvers at your disposal, but of course you can implement more of them yourself if you want to.
ParameterMethodNameResolver - capable of resolving a request parameter and using that as the method name (http://www.sf.net/index.view?testParam=testIt will result in a method testIt(HttpServletRequest, HttpServletResponse) being called). The paramName configuration parameter specifies the parameter that is inspected).
InternalPathMethodNameResolver - retrieves the filename from the path and uses that as the method name (http://www.sf.net/testing.view will result in a method testing(HttpServletRequest, HttpServletResponse) being called).
PropertiesMethodNameResolver - uses a user-defined properties object with request URLs mapped to methodnames. When the properties contain /index/welcome.html=doIt and a request to /index/welcome.html comes in, the doIt(HttpServletRequest, HttpServletResponse) method is called. This method name resolver works with the PathMatcher, so if the properties contained /**/welcom?.html, it would also have worked!
Here are a couple of examples. First, an example showing the ParameterMethodNameResolver and the delegate property, which will accept requests to urls with the parameter method included and set to retrieveIndex:
<bean id="paramResolver" class="org....mvc.multiaction.ParameterMethodNameResolver"> <property name="paramName"><value>method</value></property> </bean> <bean id="paramMultiController" class="org....mvc.multiaction.MultiActionController"> <property name="methodNameResolver"><ref bean="paramResolver"/></property> <property name="delegate"><ref bean="sampleDelegate"/></property> </bean> <bean id="sampleDelegate" class="samples.SampleDelegate"/> ## together with public class SampleDelegate { public ModelAndView retrieveIndex( HttpServletRequest req, HttpServletResponse resp) { return new ModelAndView("index", "date", new Long(System.currentTimeMillis())); } }
When using the delegates shown above, we could also use the PropertiesMethodNameResolver to match a couple of URLs to the method we defined:
<bean id="propsResolver" class="org....mvc.multiaction.PropertiesMethodNameResolver"> <property name="mappings"> <props> <prop key="/index/welcome.html">retrieveIndex</prop> <prop key="/**/notwelcome.html">retrieveIndex</prop> <prop key="/*/user?.html">retrieveIndex</prop> </props> </property> </bean> <bean id="paramMultiController" class="org....mvc.multiaction.MultiActionController"> <property name="methodNameResolver"><ref bean="propsResolver"/></property> <property name="delegate"><ref bean="sampleDelegate"/></property> </bean>
Spring's CommandControllers are a fundamental part of the Spring MVC package. Command controllers provide a way to interact with data objects and dynamically bind parameters from the HttpServletRequest to the data object specified. They perform a similar role to Struts' ActionForm, but in Spring, your data objects don't have to implement a framework-specific interface. First, let's examine what command controllers available, to get overview of what you can do with them:
AbstractCommandController - a command controller you can use to create your own command controller, capable of binding request parameters to a data object you specify. This class does not offer form functionality, it does however, offer validation features and lets you specify in the controller itself what to do with the command object that has been filled with the parameters from the request.
AbstractFormController - an abstract controller offering form submission support. Using this controller you can model forms and populate them using a command object you retrieve in the controller. After a user has filled the form, the AbstractFormController binds the fields, validates, and hands the object back to the controller to take appropriate action. Supported features are: invalid form submission (resubmission), validation, and normal form workflow. You implement methods to determine which views are used for form presentation and success. Use this controller if you need forms, but don't want to specify what views you're going to show the user in the application context.
SimpleFormController - a concrete FormController that provides even more support when creating a form with a corresponding command object. The SimpleFormController let's you specify a command object, a viewname for the form, a viewname for page you want to show the user when form submission has succeeded, and more.
AbstractWizardFormController - as the class name suggests, this is an abstract class--your WizardController should extend it. This means you have to implement the validatePage(), processFinish and processCancel methods.
You probably also want to write a contractor, which should at the very least call setPages() and setCommandName(). The former takes as its argument an array of type String. This array is the list of views which comprise your wizard. The latter takes as its argument a String, which will be used to refer to your command object from within your views.
As with any instance of AbstractFormController, you are required to use a command object - a JavaBean which will be populated with the data from your forms. You can do this in one of two ways: either call setCommandClass() from the constructor with the class of your command object, or implement the formBackingObject() method.
AbstractWizardFormController has a number of concrete methods that you may wish to override. Of these, the ones you are likely to find most useful are: referenceData which you can use to pass model data to your view in the form of a Map; getTargetPage if your wizard needs to change page order or omit pages dynamically; and onBindAndValidate if you want to override the built-in binding and validation workflow.
Finally, it is worth pointing out the setAllowDirtyBack and setAllowDirtyForward, which you can call from getTargetPage to allow users to move backwards and forwards in the wizard even if validation fails for the current page.
For a full list of methods, see the JavaDoc for AbstractWizardFormController. There is an implemented example of this wizard in the jPetStore included in the Spring distribution: org.springframework.samples.jpetstore.web.spring.OrderFormController
Using a handler mapping you can map incoming web requests to appropriate handlers. There are some handler mappings you can use out of the box, for example, the SimpleUrlHandlerMapping or the BeanNameUrlHandlerMapping, but let's first examine the general concept of a HandlerMapping.
The functionality a basic HandlerMapping provides is the delivering of a HandlerExecutionChain, which must contain the handler that matches the incoming request, and may also contain a list of handler interceptors that are applied to the request. When a request comes in, the DispatcherServlet will hand it over to the handler mapping to let it inspect the request and come up with an appropriate HandlerExecutionChain. Then the DispatcherServlet will execute the handler and interceptors in the chain (if any).
The concept of configurable handler mappings that can optionally contain interceptors (executed before or after the actual handler was executed, or both) is extremely powerful. A lot of supporting functionality can be built into custom HandlerMappings. Think of a custom handler mapping that chooses a handler not only based on the URL of the request coming in, but also on a specific state of the session associated with the request.
This section describes two of Spring's most commonly used handler mappings. They both extend the AbstractHandlerMapping and share the following properties:
interceptors: the list of interceptors to use. HandlerInterceptors are discussed in Section 13.4.3, “Adding HandlerInterceptors”.
defaultHandler: the default handler to use, when this handler mapping does not result in a matching handler.
order: based on the value of the order property (see the org.springframework.core.Ordered interface), Spring will sort all handler mappings available in the context and apply the first matching handler.
alwaysUseFullPath: if this property is set to true, Spring will use the full path within the current servlet context to find an appropriate handler. If this property is set to false (the default), the path within the current servlet mapping will be used. For example, if a servlet is mapped using /testing/* and the alwaysUseFullPath property is set to true, /testing/viewPage.html would be used, whereas if the property is set to false, /viewPage.html would be used.
urlPathHelper: using this property, you can tweak the UrlPathHelper used when inspecting URLs. Normally, you shouldn't have to change the default value.
urlDecode: the default value for this property is false. The HttpServletRequest returns request URLs and URIs that are not decoded. If you do want them to be decoded before a HandlerMapping uses them to find an appropriate handler, you have to set this to true (note that this requires JDK 1.4). The decoding method uses either the encoding specified by the request or the default ISO-8859-1 encoding scheme.
lazyInitHandlers: allows for lazy initialization of singleton handlers (prototype handlers are always lazily initialized). Default value is false.
(Note: the last four properties are only available to subclasses of org.springframework.web.servlet.handler.AbstractUrlHandlerMapping).
A very simple, but very powerful handler mapping is the BeanNameUrlHandlerMapping, which maps incoming HTTP requests to names of beans, defined in the web application context. Let's say we want to enable a user to insert an account and we've already provided an appropriate FormController (see Section 13.3.4, “CommandControllers” for more information on Command- and FormControllers) and a JSP view (or Velocity template) that renders the form. When using the BeanNameUrlHandlerMapping, we could map the HTTP request with URL http://samples.com/editaccount.form to the appropriate FormController as follows:
<beans> <bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> <bean name="/editaccount.form" class="org.springframework.web.servlet.mvc.SimpleFormController"> <property name="formView"><value>account</value></property> <property name="successView"><value>account-created</value></property> <property name="commandName"><value>Account</value></property> <property name="commandClass"><value>samples.Account</value></property> </bean> <beans>
All incoming requests for the URL /editaccount.form will now be handled by the FormController in the source listing above. Of course we have to define a servlet-mapping in web.xml as well, to let through all the requests ending with .form.
<web-app> ... <servlet> <servlet-name>sample</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- Maps the sample dispatcher to /*.form --> <servlet-mapping> <servlet-name>sample</servlet-name> <url-pattern>*.form</url-pattern> </servlet-mapping> ... </web-app>
NOTE: if you want to use the BeanNameUrlHandlerMapping, you don't necessarily have to define it in the web application context (as indicated above). By default, if no handler mapping can be found in the context, the DispatcherServlet creates a BeanNameUrlHandlerMapping for you!
A further - and much more powerful handler mapping - is the SimpleUrlHandlerMapping. This mapping is configurable in the application context and has Ant-style path matching capabilities (see the JavaDoc for org.springframework.util.PathMatcher). Here is an example:
<web-app> ... <servlet> <servlet-name>sample</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- Maps the sample dispatcher to /*.form --> <servlet-mapping> <servlet-name>sample</servlet-name> <url-pattern>*.form</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>sample</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> ... </web-app>
Allows all requests ending with .html and .form to be handled by the sample dispatcher servlet.
<beans> <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/*/account.form">editAccountFormController</prop> <prop key="/*/editaccount.form">editAccountFormController</prop> <prop key="/ex/view*.html">someViewController</prop> <prop key="/**/help.html">helpController</prop> </props> </property> </bean> <bean id="someViewController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/> <bean id="editAccountFormController" class="org.springframework.web.servlet.mvc.SimpleFormController"> <property name="formView"><value>account</value></property> <property name="successView"><value>account-created</value></property> <property name="commandName"><value>Account</value></property> <property name="commandClass"><value>samples.Account</value></property> </bean> <beans>
This handler mapping routes requests for help.html in any directory to the helpController, which is a UrlFilenameViewController (more about controllers can be found in Section 13.3, “Controllers”). Requests for a resource beginning with view, and ending with .html in the directory ex, will be routed to the someViewController. Two further mappings are defined for editAccountFormController.
Spring's handler mapping mechanism has a notion of handler interceptors, that can be extremely useful when you want to apply specific functionality to certain requests, for example, checking for a principal.
Interceptors located in the handler mapping must implement HandlerInterceptor from the org.springframework.web.servlet package. This interface defines three methods, one that will be called before the actual handler will be executed, one that will be called after the handler is executed, and one that is called after the complete request has finished. These three methods should provide enough flexibility to do all kinds of pre- and post-processing.
The preHandle method returns a boolean value. You can use this method to break or continue the processing of the execution chain. When this method returns true, the handler execution chain will continue, when it returns false, the DispatcherServlet assumes the interceptor itself has taken care of requests (and, for example, rendered an appropriate view) and does not continue executing the other interceptors and the actual handler in the execution chain.
The following example provides an interceptor that intercepts all requests and reroutes the user to a specific page if the time is not between 9 a.m. and 6 p.m.
<beans> <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="interceptors"> <list> <ref bean="officeHoursInterceptor"/> </list> </property> <property name="mappings"> <props> <prop key="/*.form">editAccountFormController</prop> <prop key="/*.view">editAccountFormController</prop> </props> </property> </bean> <bean id="officeHoursInterceptor" class="samples.TimeBasedAccessInterceptor"> <property name="openingTime"><value>9</value></property> <property name="closingTime"><value>18</value></property> </bean> <beans>
package samples; public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter { private int openingTime; private int closingTime; public void setOpeningTime(int openingTime) { this.openingTime = openingTime; } public void setClosingTime(int closingTime) { this.closingTime = closingTime; } public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Calendar cal = Calendar.getInstance(); int hour = cal.get(HOUR_OF_DAY); if (openingTime <= hour < closingTime) { return true; } else { response.sendRedirect("http://host.com/outsideOfficeHours.html"); return false; } } }
Any request coming in, will be intercepted by the TimeBasedAccessInterceptor, and if the current time is outside office hours, the user will be redirected to a static html file, saying, for example, he can only access the website during office hours.
As you can see, Spring has an adapter to make it easy for you to extend the HandlerInterceptor.
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 Java Server Pages, Velocity templates and XSLT views, for example. Chapter 14, Integrating view technologies has details of integrating various 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 Section 13.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 13.5. 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 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 ViewResolver that allows for 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 like JstlView and TilesView. The view class for all views generated by this resolver can be specified via setViewClass. See UrlBasedViewResolver's javadocs 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 the RequestDispatcher to render the view.
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="prefix"><value>/WEB-INF/jsp/</value></property> <property name="suffix"><value>.jsp</value></property> </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</value></property> <property name="defaultParentView"><value>parentView</value></property> </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 technology. 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> ### 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 tehcnologies 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 desireable to issue an HTTP redirect back to the client, before the view is rendered. This is desireable for example when one controller has been called with POSTed 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, whih 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 possiblity 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 it's 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 generally always preferable if the redirect URL at least is injected into the Controller, so that it is not baked into the controller but rather configured in the context along with view names and the like.
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 achived. 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 is 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 primarilly using another view technology, but want to still be able to in some cases force a forward to happen to a resource to be handled by the Servlet/JSP engine. Note that if you need to do this a lot though, you may also just chain multiple view resolvers.
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.
Most parts of Spring's architecture support internationalization, just as the Spring web MVC framework does. DispatcherServlet enables you to automatically resolve messages using the client's locale. This is done with LocaleResolver objects.
When a request comes in, the DispatcherServlet looks for a locale resolver and if it finds one it tries to use it to set the locale. Using the RequestContext.getLocale() method, you can always retrieve the locale that was resolved by the locale resolver.
Besides the automatic locale resolution, you can also attach an interceptor to the handler mapping (see Section 13.4.3, “Adding HandlerInterceptors” for more information on handler mapping interceptors), to change the locale under specific circumstances, based on a parameter in the request, for example.
Locale resolvers and interceptors are all defined in the org.springframework.web.servlet.i18n package, and are configured in your application context in the normal way. Here is a selection of the locale resolvers included in Spring.
This locale resolver inspects the accept-language header in the request that was sent by the browser of the client. Usually this header field contains the locale of the client's operating system.
This locale resolver inspects a Cookie that might exist on the client, to see if a locale is specified. If so, it uses that specific locale. Using the properties of this locale resolver, you can specify the name of the cookie, as well as the maximum age.
<bean id="localeResolver"> <property name="cookieName"><value>clientlanguage</value></property> <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) --> <property name="cookieMaxAge"><value>100000</value></property> </bean>
This is an example of defining a CookieLocaleResolver.
Table 13.6. Special beans in the WebApplicationContext
Property | Default | Description |
---|---|---|
cookieName | classname + LOCALE | The name of the cookie |
cookieMaxAge | Integer.MAX_INT | The maximum time a cookie will stay persistent on the client. If -1 is specified, the cookie will not be persisted. It will only be available until the client shuts down his or her browser. |
cookiePath | / | Using this parameter, you can limit the visibility of the cookie to a certain part of your site. When cookiePath is specified, the cookie will only be visible to that path, and the paths below it. |
The SessionLocaleResolver allows you to retrieve locales from the session that might be associated with the user's request.
You can build in changing of locales using the LocaleChangeInterceptor. This interceptor needs to be added to one of the handler mappings (see Section 13.4, “Handler mappings”). It will detect a parameter in the request and change the locale (it calls setLocale() on the LocaleResolver that also exists in the context).
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"> <property name="paramName"><value>siteLanguage</value></property> </bean> <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="interceptors"> <list> <ref bean="localeChangeInterceptor"/> </list> </property> <property name="mappings"> <props> <prop key="/**/*.view">someController</prop> </props> </property> </bean>
All calls to all *.view resources containing a parameter named siteLanguage will now change the locale. So a call to http://www.sf.net/home.view?siteLanguage=nl will change the site language to Dutch.
The theme support provided by the Spring web MVC framework enables you to further enhance the user experience by allowing the look and feel of your application to be themed. A theme is basically a collection of static resources affecting the visual style of the application, typically style sheets and images.
When you want to use themes in your web application you'll have to setup a org.springframework.ui.context.ThemeSource. The WebApplicationContext interface extends ThemeSource but delegates its responsabilities to a dedicated implementation. By default the delegate will be a org.springframework.ui.context.support.ResourceBundleThemeSource that loads properties files from the root of the classpath. If you want to use a custom ThemeSource implementation or if you need to configure the basename prefix of the ResourceBundleThemeSource, you can register a bean in the application context with the reserved name "themeSource". The web application context will automatically detect that bean and start using it.
When using the ResourceBundleThemeSource, a theme is defined in a simple properties file. The properties file lists the resources that make up the theme. Here's an example:
styleSheet=/themes/cool/style.css background=/themes/cool/img/coolBg.jpg
The keys of the properties are the names used to refer to the themed elements from view code. For a JSP this would typically be done using the spring:theme custom tag, which is very similar to the spring:message tag. The following JSP fragment uses the theme defined above to customize the look and feel:
<taglib prefix="spring" uri="http://www.springframework.org/tags"%> <html> <head> <link rel="stylesheet" href="<spring:theme code="styleSheet"/>" type="text/css"/> </head> <body background="<spring:theme code="background"/>"> ... </body> </html>
By default, the ResourceBundleThemeSource uses an empty basename prefix. As a result the properties files will be loaded from the root of the classpath, so we'll have to put our cool.properties theme definition in a directory at the root of the classpath, e.g. in /WEB-INF/classes. Note that the ResourceBundleThemeSource uses the standard Java resource bundle loading mechanism, allowing for full internationalisation of themes. For instance, we could have a /WEB-INF/classes/cool_nl.properties that references a special background image, e.g. with Dutch text on it.
Now that we have our themes defined, the only thing left to do is decide which theme to use. The DispatcherServlet will look for a bean named "themeResolver" to find out which ThemeResolver implementation to use. A theme resolver works in much the same way as a LocalResolver. It can detect the theme that should be used for a particular request and can also alter the request's theme. The following theme resolvers are provided by Spring:
Table 13.7. ThemeResolver implementations
Class | Description |
---|---|
FixedThemeResolver | Selects a fixed theme, set using the "defaultThemeName" property. |
SessionThemeResolver | The theme is maintained in the users HTTP session. It only needs to be set once for each session, but is not persisted between sessions. |
CookieThemeResolver | The selected theme is stored in a cookie on the client's machine. |
Spring also provides a ThemeChangeInterceptor, which allows changing the theme on every request by including a simple request parameter.
Spring has built-in multipart support to handle fileuploads in web applications. The design for the multipart support is done with pluggable MultipartResolver objects, defined in the org.springframework.web.multipart package. Out of the box, Spring provides MultipartResolvers for use with Commons FileUpload (http://jakarta.apache.org/commons/fileupload) and COS FileUpload (http://www.servlets.com/cos). How uploading files is supported will be described in the rest of this chapter.
By default, no multipart handling will be done by Spring, as some developers will want to handle multiparts themselves. You will have to enable it yourself by adding a multipart resolver to the web application's context. After you have done that, each request will be inspected to see if it contains a multipart. If no multipart is found, the request will continue as expected. However, if a multipart is found in the request, the MultipartResolver that has been declared in your context will be used. After that, the multipart attribute in your request will be treated like any other attribute.
The following example shows how to use the CommonsMultipartResolver:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- one of the properties available; the maximum file size in bytes --> <property name="maxUploadSize"> <value>100000</value> </property> </bean>
This is an example using the CosMultipartResolver:
<bean id="multipartResolver" class="org.springframework.web.multipart.cos.CosMultipartResolver"> <!-- one of the properties available; the maximum file size in bytes --> <property name="maxUploadSize"> <value>100000</value> </property> </bean>
Of course you need to stick the appropriate jars in your classpath for the multipart resolver to work. In the case of the CommonsMultipartResolver, you need to use commons-fileupload.jar, while in the case of the CosMultipartResolver, use cos.jar.
Now that you have seen how to set Spring up to handle multipart requests, let's talk about how to actually use it. When the Spring DispatcherServlet detects a Multipart request, it activates the resolver that has been declared in your context and hands over the request. What it basically does is wrap the current HttpServletRequest into a MultipartHttpServletRequest that has support for multiparts. Using the MultipartHttpServletRequest you can get information about the multiparts contained by this request and actually get the multiparts themselves in your controllers.
After the MultipartResolver has finished doing its job, the request will be processed like any other. To use it, you create a form with an upload field, then let Spring bind the file on your form. Just as with any other property that's not automagically convertible to a String or primitive type, to be able to put binary data in your beans you have to register a custom editor with the ServletRequestDatabinder. There are a couple of editors available for handling files and setting the results on a bean. There's a StringMultipartEditor capable of converting files to Strings (using a user-defined character set) and there is a ByteArrayMultipartEditor which converts files to byte arrays. They function just as the CustomDateEditor does.
So, to be able to upload files using a form in a website, declare the resolver, a url mapping to a controller that will process the bean, and the controller itself.
<beans> ... <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/upload.form">fileUploadController</prop> </props> </property> </bean> <bean id="fileUploadController" class="examples.FileUploadController"> <property name="commandClass"><value>examples.FileUploadBean</value></property> <property name="formView"><value>fileuploadform</value></property> <property name="successView"><value>confirmation</value></property> </bean> </beans>
After that, create the controller and the actual bean to hold the file property
// snippet from FileUploadController public class FileUploadController extends SimpleFormController { protected ModelAndView onSubmit( HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws ServletException, IOException { // cast the bean FileUploadBean bean = (FileUploadBean)command; // let's see if there's content there byte[] file = bean.getFile(); if (file == null) { // hmm, that's strange, the user did not upload anything } // well, let's do nothing with the bean for now and return: return super.onSubmit(request, response, command, errors); } protected void initBinder( HttpServletRequest request, ServletRequestDataBinder binder) throws ServletException { // to actually be able to convert Multipart instance to byte[] // we have to register a custom editor (in this case the // ByteArrayMultipartEditor binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor()); // now Spring knows how to handle multipart object and convert them } } // snippet from FileUploadBean public class FileUploadBean { private byte[] file; public void setFile(byte[] file) { this.file = file; } public byte[] getFile() { return file; } }
As you can see, the FileUploadBean has a property typed byte[] that holds the file. The controller registers a custom editor to let Spring know how to actually convert the multipart objects the resolver has found to properties specified by the bean. In these examples, nothing is done with the byte[] property of the bean itself, but in practice you can do whatever you want (save it in a database, mail it to somebody, etc).
But we're still not finished. To actually let the user upload something, we have to create a form:
<html> <head> <title>Upload a file please</title> </head> <body> <h1>Please upload a file</h1> <form method="post" action="upload.form" enctype="multipart/form-data"> <input type="file" name="file"/> <input type="submit"/> </form> </body> </html>
As you can see, we've created a field named after the property of the bean that holds the byte[]. Furthermore we've added the encoding attribute which is necessary to let the browser know how to encode the multipart fields (do not forget this!). Now everything should work.
Spring provides HandlerExceptionResolvers to ease the pain of unexpected exceptions occurring while your request is being handled by a controller which matched the request. HandlerExceptionResolvers somewhat resemble the exception mappings you can define in the web application descriptor web.xml. However, they provide a more flexible way to handle exceptions. They provide information about what handler was executing when the exception was thrown. Furthermore, a programmatic way of handling exception gives you many more options for how to respond appropriately before the request is forwarded to another URL (the same end result as when using the servlet specific exception mappings).
Besides implementing the HandlerExceptionResolver, which is only a matter of implementing the resolveException(Exception, Handler) method and returning a ModelAndView, you may also use the SimpleMappingExceptionResolver. This resolver enables you to take the class name of any exception that might be thrown and map it to a view name. This is functionally equivalent to the exception mapping feature from the Servlet API, but it's also possible to implement more fine grained mappings of exceptions from different handlers.