Spring 2.5 introduces an annotation-based programming model for MVC
controllers, using annotations such as
@RequestMapping
,
@RequestParam
,
@ModelAttribute
, etc. This annotation
support is available for both Servlet MVC and Portlet MVC. Controllers
implemented in this style do not have to extend specific base classes or
implement specific interfaces. Furthermore, they do not usually have
direct dependencies on Servlet or Portlet API's, although they can easily
get access to Servlet or Portlet facilities if desired.
Tip | |
---|---|
The Spring distribution ships with the
PetPortal sample, which is a portal application that takes
advantage of the annotation support described in this section, in the context
of simple form processing. You can find the PetPortal
application in the |
The following sections document these annotations and how they are most commonly used in a Portlet environment.
@RequestMapping
will only be processed
if a corresponding HandlerMapping
(for type level annotations)
and/or HandlerAdapter
(for method level annotations) is
present in the dispatcher. This is the case by default in both
DispatcherServlet
and DispatcherPortlet
.
However, if you are defining custom HandlerMappings
or
HandlerAdapters
, then you need to make sure that a
corresponding custom DefaultAnnotationHandlerMapping
and/or AnnotationMethodHandlerAdapter
is defined as well
- provided that you intend to use @RequestMapping
.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean class="org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping"/> <bean class="org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter"/> // ... (controller bean definitions) ... </beans>
Defining a DefaultAnnotationHandlerMapping
and/or AnnotationMethodHandlerAdapter
explicitly
also makes sense if you would like to customize the mapping strategy, e.g.
specifying a custom WebBindingInitializer
(see below).
The @Controller
annotation indicates
that a particular class serves the role of a controller.
There is no need to extend any controller base class or reference the
Portlet API. You are of course still able to reference Portlet-specific
features if you need to.
The basic purpose of the @Controller
annotation is to act as a stereotype for the annotated class, indicating
its role. The dispatcher will scan such annotated classes for mapped
methods, detecting @RequestMapping
annotations (see the next section).
Annotated controller beans may be defined explicitly,
using a standard Spring bean definition in the dispatcher's context.
However, the @Controller
stereotype also
allows for autodetection, aligned with Spring 2.5's general support for
detecting component classes in the classpath and auto-registering bean
definitions for them.
To enable autodetection of such annotated controllers, you have to add component scanning to your configuration. This is easily achieved by using the spring-context schema as shown in the following XML snippet:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:component-scan base-package="org.springframework.samples.petportal.portlet"/> // ... </beans>
The @RequestMapping
annotation is used
to map portlet modes like 'VIEW'/'EDIT' onto an entire class or a particular
handler method. Typically the type-level annotation maps a specific mode
(or mode plus parameter condition) onto a form controller, with additional
method-level annotations 'narrowing' the primary mapping for specific
portlet request parameters.
Tip | |
---|---|
In the following discussion, we'll focus on controllers that are based on annotated handler methods. |
The following is an example of a form controller from the PetPortal sample application using this annotation:
@Controller @RequestMapping("EDIT") @SessionAttributes("site") public class PetSitesEditController { private Properties petSites; public void setPetSites(Properties petSites) { this.petSites = petSites; } @ModelAttribute("petSites") public Properties getPetSites() { return this.petSites; } @RequestMapping // default (action=list) public String showPetSites() { return "petSitesEdit"; } @RequestMapping(params = "action=add") // render phase public String showSiteForm(Model model) { // Used for the initial form as well as for redisplaying with errors. if (!model.containsAttribute("site")) { model.addAttribute("site", new PetSite()); } return "petSitesAdd"; } @RequestMapping(params = "action=add") // action phase public void populateSite( @ModelAttribute("site") PetSite petSite, BindingResult result, SessionStatus status, ActionResponse response) { new PetSiteValidator().validate(petSite, result); if (!result.hasErrors()) { this.petSites.put(petSite.getName(), petSite.getUrl()); status.setComplete(); response.setRenderParameter("action", "list"); } } @RequestMapping(params = "action=delete") public void removeSite(@RequestParam("site") String site, ActionResponse response) { this.petSites.remove(site); response.setRenderParameter("action", "list"); } }
Handler methods which are annotated with
@RequestMapping
are allowed to have very flexible
signatures. They may have arguments of the following types, in arbitrary
order (except for validation results, which need to follow right after
the corresponding command object, if desired):
Request and/or response objects (Portlet API). You may choose any specific request/response type, e.g. PortletRequest / ActionRequest / RenderRequest. An explicitly declared action/render argument is also used for mapping specific request types onto a handler method (in case of no other information given that differentiates between action and render requests).
Session object (Portlet API): of type PortletSession. An argument
of this type will enforce the presence of a corresponding session.
As a consequence, such an argument will never be null
.
org.springframework.web.context.request.WebRequest
or org.springframework.web.context.request.NativeWebRequest
.
Allows for generic request parameter access as well as request/session
attribute access, without ties to the native Servlet/Portlet API.
java.util.Locale
for the current request
locale (the portal locale in a Portlet environment).
java.io.InputStream
/
java.io.Reader
for access to the request's content.
This will be the raw InputStream/Reader as exposed by the Portlet API.
java.io.OutputStream
/
java.io.Writer
for generating the response's content.
This will be the raw OutputStream/Writer as exposed by the Portlet API.
@RequestParam
annotated parameters
for access to specific Portlet request parameters. Parameter values
will be converted to the declared method argument type.
java.util.Map
/
org.springframework.ui.Model
/
org.springframework.ui.ModelMap
for
enriching the implicit model that will be exposed to the web view.
Command/form objects to bind parameters to: as bean
properties or fields, with customizable type conversion, depending
on @InitBinder
methods and/or the
HandlerAdapter configuration - see the
"webBindingInitializer
" property on
AnnotationMethodHandlerAdapter
. Such
command objects along with their validation results will be
exposed as model attributes, by default using the non-qualified
command class name in property notation (e.g. "orderAddress" for
type "mypackage.OrderAddress"). Specify a parameter-level
ModelAttribute
annotation for declaring a
specific model attribute name.
org.springframework.validation.Errors
/
org.springframework.validation.BindingResult
validation results for a preceding command/form object (the
immediate preceding argument).
org.springframework.web.bind.support.SessionStatus
status handle for marking form processing as complete (triggering
the cleanup of session attributes that have been indicated by the
@SessionAttributes
annotation at the
handler type level).
The following return types are supported for handler methods:
A ModelAndView
object, with the model implicitly
enriched with command objects and the results of @ModelAttribute
annotated reference data accessor methods.
A Model
object, with the view name implicitly
determined through a RequestToViewNameTranslator
and the model implicitly enriched with command objects and the results of
@ModelAttribute
annotated reference data accessor methods.
A Map
object for exposing a model, with the view name
implicitly determined through a RequestToViewNameTranslator
and the model implicitly enriched with command objects and the results of
@ModelAttribute
annotated reference data accessor methods.
A View
object, with the model implicitly
determined through command objects and @ModelAttribute
annotated reference data accessor methods. The handler method may also
programmatically enrich the model by declaring a Model
argument (see above).
A String
value which is interpreted as view name,
with the model implicitly determined through command objects and
@ModelAttribute
annotated reference data accessor methods.
The handler method may also programmatically enrich the model by declaring a
Model
argument (see above).
void
if the method handles the response itself
(e.g. by writing the response content directly).
Any other return type will be considered as single model attribute
to be exposed to the view, using the attribute name specified through
@ModelAttribute
at the method level (or the default
attribute name based on the return type's class name otherwise). The model
will be implicitly enriched with command objects and the results of
@ModelAttribute
annotated reference data accessor methods.
The @RequestParam
annotation is used to
bind request parameters to a method parameter in your controller.
The following code snippet from the PetPortal sample application shows the usage:
@Controller @RequestMapping("EDIT") @SessionAttributes("site") public class PetSitesEditController { // ... public void removeSite(@RequestParam("site") String site, ActionResponse response) { this.petSites.remove(site); response.setRenderParameter("action", "list"); } // ... }
Parameters using this annotation are required by default, but you
can specify that a parameter is optional by setting
@RequestParam
's
required
attribute to false
(e.g.,
@RequestParam(value="id", required="false")
).
@ModelAttribute
has two usage scenarios in
controllers. When placed on a method parameter,
@ModelAttribute
is used to map a model attribute
to the specific, annotated method parameter (see the
processSubmit()
method below). This is how the
controller gets a reference to the object holding the data entered in
the form. In addition, the parameter can be declared as the specific
type of the form backing object rather than as a generic
java.lang.Object
, thus increasing type
safety.
@ModelAttribute
is also used at the method
level to provide reference data for the model (see
the populatePetTypes()
method below). For this usage
the method signature can contain the same types as documented above for
the @RequestMapping
annotation.
Note: @ModelAttribute
annotated methods will be executed before the
chosen @RequestMapping
annotated handler method.
They effectively pre-populate the implicit model with specific attributes,
often loaded from a database. Such an attribute can then already be
accessed through @ModelAttribute
annotated
handler method parameters in the chosen handler method, potentially
with binding and validation applied to it.
The following code snippet shows these two usages of this annotation:
@Controller @RequestMapping("EDIT") @SessionAttributes("site") public class PetSitesEditController { // ... @ModelAttribute("petSites") public Properties getPetSites() { return this.petSites; } @RequestMapping(params = "action=add") // action phase public void populateSite( @ModelAttribute("site") PetSite petSite, BindingResult result, SessionStatus status, ActionResponse response) { new PetSiteValidator().validate(petSite, result); if (!result.hasErrors()) { this.petSites.put(petSite.getName(), petSite.getUrl()); status.setComplete(); response.setRenderParameter("action", "list"); } } }
The type-level @SessionAttributes
annotation declares session attributes used by a specific handler. This
will typically list the names of model attributes which should be
transparently stored in the session or some conversational storage,
serving as form-backing beans between subsequent requests.
The following code snippet shows the usage of this annotation:
@Controller @RequestMapping("EDIT") @SessionAttributes("site") public class PetSitesEditController { // ... }
To customize request parameter binding with PropertyEditors, etc.
via Spring's WebDataBinder
, you can either use
@InitBinder
-annotated methods within your
controller or externalize your configuration by providing a custom
WebBindingInitializer
.
Annotating controller methods with
@InitBinder
allows you to configure web
data binding directly within your controller class.
@InitBinder
identifies methods which
initialize the WebDataBinder
which will be used
for populating command and form object arguments of annotated handler
methods.
Such init-binder methods support all arguments that
@RequestMapping
supports, except for
command/form objects and corresponding validation result objects.
Init-binder methods must not have a return value. Thus, they are
usually declared as void
. Typical arguments include
WebDataBinder
in combination with
WebRequest
or
java.util.Locale
, allowing code to register
context-specific editors.
The following example demonstrates the use of
@InitBinder
for configuring a
CustomDateEditor
for all
java.util.Date
form properties.
@Controller public class MyFormController { @InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); } // ... }
To externalize data binding initialization, you can provide a
custom implementation of the
WebBindingInitializer
interface, which
you then enable by supplying a custom bean configuration for an
AnnotationMethodHandlerAdapter
, thus overriding
the default configuration.