Spring's web framework is designed around a DispatcherServlet that dispatches requests to handlers, with configurable handler mappings, view resolution, locale and theme resolution as well 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 take any object as 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, e.g. 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's 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 just by making them bean properties of the respective Action class. Finally, the same Action instance that handles the request gets used for evaluation and form population in the view. Thus, reference data needs to be modelled as bean properties of the Action too. These are arguably too many roles in one object.
Regarding views: 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, reference data, etc). 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: Be it JSP, Velocity, or anything else - every renderer can be integrated directly. The model Map simply gets transformed into an appropriate format, like JSP request attributes or a Velocity template model.
Many teams will try to leverage their investments in terms of know-how and tools, both for existing projects and for new ones. Concretely, there are not only a large number of books and tools for Struts but also a lot of developers that have experience with it. Thus, if you can live with Struts's architectural flaws, it can still be a viable choice for the web layer. The same applies to WebWork and other web 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 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, therefore no dedicated integration: From the view of the web layer, you'll simply use Spring as a library, with the root application context instance as 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 usage, it just addresses the many areas that the pure web 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 e.g. the transaction abstraction with JDBC or Hibernate.
If just focusing on the web support, some of the Spring's unique features are:
Clear separation of roles: controller vs validator vs command object vs form object vs model object, DispatcherServlet vs handler mapping vs view resolver, etc.
Powerful and straightforward configuration of both framework and application classes as JavaBeans, including easy in-between referencing via an application context, e.g. 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 Action/ActionForm 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 special ActionForm subclasses.
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, customizable view resolution: flexible model transfer via name/value Map, handler mapping and view resolution strategies from simple to sophisticated instead of one single way.
Customizable locale and theme resolution, support for JSPs with and without Spring tag library, support for JSTL, support for Velocity without the need for extra bridges, etc.
Simple but powerful tag library that avoids HTML generation at any cost, allowing for maximum flexibility in terms of markup code.
Spring's web framework is - like many other web frameworks - a request driven web framework, designed around a servlet that dispatches requests to controllers and offers other functionality facilitating the development of webapplications. 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.
Servlets are declared in the web.xml of your webapplication, so is the DispatcherServlet. 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. Then, the DispatcherServlet needs to be configured. As illustrated in Section 3.10, “Introduction to the ApplicationContext”, ApplicationContexts in Spring can be scoped. In the web framework, each DispatcherServlet has its own WebApplicationContext, which contains the DispatcherServlet configuration beans. The default BeanFactory used by the DispatcherServlet is the XmlBeanFactory and the DispatcherServlet will on initialization look for a file named [servlet-name]-servlet.xml in the WEB-INF directory of your web application. The default values used by the DispatcherServlet can be modified by using the servlet initialization parameters (see below for more information).
The WebApplicationContext is just an ordinary ApplicationContext that has some extra features necessary for webapplications. It differs from a normal ApplicationContext in that it is capable of resolving themes (see Section 12.7, “Using themes”), and that is knows to which servlet it is associated (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. Those beans are included in the Spring framework and (optionally) have to be configured in the WebApplicationContext, just as any other bean would have to 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 those.
Table 12.1. Special beans in the WebApplicationContext
Expression | Explanation |
---|---|
handler mapping(s) | (Section 12.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 12.3, “Controllers”) the beans providing the actual functionality (or at least, access to the functionality) as part of the MVC triad |
view resolver | (Section 12.5, “Views and resolving them”) capable of resolving view names and needed by the DispatcherServlet to resolves those views with |
locale resolver | (Section 12.6, “Using locales”) capable of resolves the locale a client is using, in order to be able to offer internationalized views |
theme resolver | (Section 12.7, “Using themes”) capable of resolving themes your webapplication can use e.g. to offer personalized layouts |
multipart resolver | (Section 12.8, “Spring's multipart (fileupload) support”) offers functionality to process file uploads from HTML forms |
handlerexception resolver | (Section 12.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 a DispatcherServlet is supposed to handle it:
The WebApplicationContext is searched for and bound in the request as an attribute in order for controller and other elements in the chain of process to use it. 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 chain resolve the locale to use when processing the request (rendering the view, preparing data, etcetera). If you don't use the resolver, it won't affect anything, so if you don't need locale resolving, just don't bother
The theme resolver is bound to the request to let e.g. views determine which theme to use (if you don't needs themes, don't bother, the resolver is just bound and does not affect anything if you don't use it)
If a multipart resolver is specified, the request is inspected for multiparts and if so, it is wrapped in a MultipartHttpServletRequest for further processing by other elements in the chain (more information about multipart handling is provided below)
An appropriate handler is searched for. If a handler is found, it execution chain associated to 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 was returned (which could be the result of a pre- or postprocessor intercepting the request because of for instance security reasons), no view is rendered as well, 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 those 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 of all lookup an appropriate handler mapping and test if the handler that matched implements the interface LastModified and if so, the value the 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 12.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 controller is part of the MVC design pattern. Controllers define application behavior, or at least provide users with 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.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 just states one single method that should be capable of handling a request and return an appropriate model and view. Those three concepts are the basis for the Spring MVC implemente; ModelAndView and Controller. While the Controller interface is quite abstract, Spring offers a lot of controllers that already contain a lot of functionality you might need. The controller interface just define the most commons functionality offered by every controller: the functionality of 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 instance the setting of the mimetype.
Table 12.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 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 | tweaking of your controllers specifying the HTTP 1.0 compatible "Expires" header. By default it's set to true, so you won't have to touch it |
useCacheHeader | tweaking of your controllers specifying the HTTP 1.1 compatible "Cache-Control" header. By default this is set to true so you won't really have to touch it |
the last two properties are actually part of the WebContentGenerator which is the superclass of AbstractController but to keeps things clear...
When using the AbstractController as a baseclass for your controllers (which is not recommended since there are a lot of other controller that might already do the job for your) you only have to override the handleRequestInternal(HttpServletRequest, HttpServletResponse)-method and implement your logic code and return a ModelAndView object there. A short example consisting of a class and a declaration in the webapplicationcontext.
package samples; public class SampleController extends AbstractController { public ModelAndView handleRequestInternal( HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mav = new ModelAndView("foo", new HashMap()); } }
<bean id="sampleController" class="samples.SampleController"> <property name="cacheSeconds"><value>120</value</property> </bean>
The class above and the declaration in the webapplicationcontext is all you need to do besides setting up a handler mapping (see Section 12.4, “Handler mappings”) to get this very simple controller working. This controller will generates caching directives telling the client to cache things for 2 minutes before rechecking. This controller furthermore returns an hard-coded view (hmm, not so nice), named index (see Section 12.5, “Views and resolving them” for more information about views).
Besides the AbstractController - which you could of course extend, although a more concrete controller might offer you more functionality - there are a couple of other simple controllers that might ease the pain of developing simple MVC applications. The ParameterizableViewController basically is the same as the one in the example above, except for the fact that you can specify its view name that it'll be returning in the webapplicationcontext (ahhh, no need to hard-code the viewname).
The FileNameViewController 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.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're having a lot of commons functionality in one controller, but want to have multiple entry points to the controller to tweak behavior for instance.
Table 12.4. Features offered by the MultiActionController
Feature | Explanation |
---|---|
delegate | there's 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 case you don't need this configuration parameter), or you define a delegate oject, on which methods resolved by the Resolver will be invoked. If you choose to enter 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 will 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'll confuse the MultiActionController. Furthermore, you can define exception handlers capable of handling exception that will be thrown form a method you specify. Exception handler methods need to return a ModelAndView object, just as any other action method and will 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 resolver to your disposal, but of course you can implement more of them yourself if you want.
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). Use the paramName configuration parameter to tweak the parameter that's 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 (see Section 12.10.1, “A little story about the pathmatcher”) so if the properties contained /**/welcom?.html it would also have worked!
A couple of examples. First of all one 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"/> </bean> <bean id="sampleDelegate" class="samples.SampleDelegate"/> ## together with public class SampleDelegate { public ModelAndView retrieveIndex( HttpServletRequest req, HttpServletResponse resp) { rerurn new ModelAndView("index", "date", new Long(System.currentTimeMillis())); } }
When using the delegates shown above, we could also use the PropertiesMethodNameRseolver 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"/> </bean>
Spring's CommandControllers are a fundamental part of the Spring MVC package. Command controllers provide a way to interact with dataobjects and dynamically bind parameters from the HttpServletRequest to the dataobject you're specifying. This compares to Struts's actionforms, where in Spring, you don't have to implement any interface of superclasses to do databinding. First, let's examine what command controllers available, just to get clear picture 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're specifying. 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 dataobject 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 dataobject you're retrieving in the controller. After a user has filled the form, the AbstractFormController binds the fields, validates and hands the object back to you - the controller - to take appropriate action. Supported features are invalid form submission (resubmission), validation, and the right workflow a form always has. What views you tie to your AbstractFormController you decide yourself. Use this controller if you need forms, but don't want to specify what views you're going to show the user in the applicationcontext
SimpleFormController - an even more concrete FormCotnroller that helps you creating a form with corresponding data object even more. 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 formsubmission 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 both the validatePage(), processFinish as well as processCancel methods.
Probably you 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.java
Using a handler mapping you can map incoming web requests to appropriate handlers. There are some handler mapping you can use, 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, first of all containing one handler that matched the incoming request. The second (but optional) element a handler execution chain will contain is a list of handler interceptor that should be 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. When done, 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-in in 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 often used handler mapping. They both extend the AbstractHandlerMapping and share the following properties
interceptors: the list of interceptors to use. HandlerInterceptors are discussed further ahead
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 mapping available in the context and apply them in them one after the other.
alwaysUseFullPath: based on this setting, Spring will either use the full path within the current servlet context (if set to true) or the path within the current servlet mapping (false, the default value). If for example a servlet is mapped using /testing/* and you've set the alwaysUseFullPath property to true, /testing/viewPage.html will match, whereas /viewPage.html will only match if you leave the default value (false) in place (Note: this property is only available for the org.springframework.web.servlet.handler.AbstractUrlHandlerMapping and its subclasses)
urlPathHelper: using this property, you can tweak the UrlPathHelper used when inspecting URLs. Normally, you shouldn't have to change the default value. (Note: this property is only available for the org.springframework.web.servlet.handler.AbstractUrlHandlerMapping and its subclasses)
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 will use them to find an appropriate handler, you have to set this to true (this requires JDK 1.4 however). Uses either the encoding specified by the request or the default ISO-8859-1 encoding scheme. (Note: this property is only available for the org.springframework.web.servlet.handler.AbstractUrlHandlerMapping and its subclasses)
lazyInitHandlers: allows for lazy initialization of singleton handlers (prototype handlers are always lazily initialized). Default value is false (Note: this property is only available for the org.springframework.web.servlet.handler.AbstractUrlHandlerMapping and its subclasses)
A very simple, but very powerful handler mapping is the BeanNameUrlHandlerMapping, which maps incoming HTTP requests to names of beans, defined in the webapplicationcontext. Let's say we want to enable a user to insert an account and we've already provided an appropriate FormController (see Section 12.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 webapplicationcontext (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 Section 12.10.1, “A little story about the pathmatcher”). A couple of example will probably makes thing clear enough:
<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 first of all reroutes all requests in all directories for a file named help.html to the someViewController, which is a UrlFilenameViewController (more about that can be found in Section 12.3, “Controllers”). Also, all requests for a resource beginning with view, ending with .html, in the directory ex, will be rerouted to that specific controller. Furthermore, two mappings have been defined that will match with the editAccountFormController.
The handler mapping also has a notion of handler interceptors, that can be extremely useful when you want to apply specific functionality to all requests, for example the checking for a principal or something alike.
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. Those three methods should provide you with enough flexibility to do all kinds of pre- and post-processing.
The preHandle method has a boolean return value. Using this value, you can tweak the behavior of the execution chain. When returning true, the handler execution chain will continue, when returning false, the DispatcherServlet assumes the interceptor itself has taken care of requests (and for instance rendered an appropriate view) and does not continue with 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 redirect to a static html file, saying for instance 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.
No MVC framework for web applications is without a way to address views. Spring provides view resolvers, which enable you to render models in a browser without tying yourself 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 13, Integrating view technologies has details of integrating various view technologies.
The two classes which are important to the way Spring handles views are the ViewResolver and the View. The View interface addresses the preparation of the request and hands the request over to one of the view technologies. The ViewResolver provides a mapping between view names and actual views.
As discussed before, all controllers in the SpringWeb framework, return a ModelAndView instance. Views in Spring are addressed by a view name and are resolved by a viewresolver. Spring comes with quite a few view resolvers. We'll list most of them and then provide a couple of examples.
Table 12.5. View resolvers
ViewResolver | Description |
---|---|
AbstractCachingViewResolver | Abstract view resolver taking care of caching views. Lots of views need preparation before they can be used, extending from this viewresolver enables caching of views |
XmlViewResolver | Implementation of ViewResolver that accepts a config file written in XML to the same DTD as Spring's bean factories |
ResourceBundleViewResolver | 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 |
UrlBasedViewResolver | 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 | 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 | 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 the UrlBasedViewResolver. This view resolver translates view names 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'll send the request to /WEB-INF/jsp/test.jsp.
When mixing different view technologies in a webapplications, 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 view in the properties file sort of extend. This way you can specify a default view class for instance.
A note on caching: subclasses of AbstractCachingViewResolver cache view instances they've 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.
Most parts of Spring's architecture support internationalization, just as the Spring web framework does. SpringWEB 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 and 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 12.4.3, “Adding HandlerInterceptors” for more info on that), to change the locale under specific circumstances, based on a parameter occurring 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 there's a locale 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 12.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, at least, only 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 |
The SessionLocaleResolver allows you to retrieve locales from the session that might be associated to 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 12.4, “Handler mappings”) and 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 local="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.
Spring has built-in multipart support to handle fileuploads in webapplications. The design for the multipart support is done with pluggable MultipartResovler objects, defined in the org.springframework.web.multipart package. Out of the box, Spring provides MultipartResolver 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'll have to enable it yourself by adding a multipartresolver to the webapplication's context. After you've done that, each request will be inspected for a multipart that it might contain. If no such 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 resolve. After that, the multipart attribute in your request will be treated as any other attributes.
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 multipartresolver 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's 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 holding 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, etcetera).
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 (dont' forget this!). Right now everything should work.
Spring provides HandlerExceptionResolvers to ease the pain of unexpected exceptions occuring while your request is being handled by a controller which matched the request. HandlerExceptionResolvers somewhat resemble the exception-mappings you can define in the webapplication descriptor web.xml. However, they provide a more flexible to handle exceptions. 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 exception from different handlers.