16.11 Annotation-based controller configuration

There is a current trend to favor annotations over XML files for some types of configuration data. To facilitate this, Spring is now (since 2.5) providing support for configuring the MVC framework components using annotations.

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]Tip

The Spring distribution ships with the PetClinic sample, which is a web application that takes advantage of the annotation support described in this section, in the context of simple form processing. You can find the PetClinic application in the 'samples/petclinic' directory.

For a further sample application that builds on annotation-based Web MVC, check out imagedb. The focus in that sample is on stateless multi-action controllers, including the processing of multipart file uploads. You can find the imagedb application in the 'samples/imagedb' directory.

The following sections document these annotations and how they are most commonly used in a Servlet environment.

16.11.1 Setting up the dispatcher for annotation support

@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-2.5.xsd">

    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>

    <bean class="org.springframework.web.servlet.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 PathMatcher or WebBindingInitializer (see below).

16.11.2 Defining a controller with @Controller

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 Servlet API. You are of course still able to reference Servlet-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-2.5.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:component-scan base-package="org.springframework.samples.petclinic.web"/>

    // ...

</beans>

16.11.3 Mapping requests with @RequestMapping

The @RequestMapping annotation is used to map URLs like '/editPet.do' onto an entire class or a particular handler method. Typically the type-level annotation maps a specific request path (or path pattern) onto a form controller, with additional method-level annotations 'narrowing' the primary mapping for a specific HTTP method request method ("GET"/"POST") or specific HTTP request parameters.

[Tip]Tip

@RequestMapping at the type level may be used for plain implementations of the Controller interface as well. In this case, the request processing code would follow the traditional handleRequest signature, while the controller's mapping would be expressed through an @RequestMapping annotation. This works for pre-built Controller base classes, such as SimpleFormController, too.

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 PetClinic sample application using this annotation:

@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {

    private final Clinic clinic;

    @Autowired
    public EditPetForm(Clinic clinic) {
        this.clinic = clinic;
    }

    @ModelAttribute("types")
    public Collection<PetType> populatePetTypes() {
        return this.clinic.getPetTypes();
    }

    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    @RequestMapping(method = RequestMethod.POST)
    public String processSubmit(
            @ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {

        new PetValidator().validate(pet, result);
        if (result.hasErrors()) {
            return "petForm";
        }
        else {
            this.clinic.storePet(pet);
            status.setComplete();
            return "redirect:owner.do?ownerId=" + pet.getOwner().getId();
        }
    }
}

For a traditional multi-action controller the URLs are typically mapped directly on the methods since the controller responds to multiple URLs. The following is an example of a multi-action controller from the PetClinic sample application using @RequestMapping:

@Controller
public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
        this.clinic = clinic;
    }

    /**
     * Custom handler for the welcome view.
     * Note that this handler relies on the RequestToViewNameTranslator to
     * determine the logical view name based on the request URL: "/welcome.do"
     * -> "welcome".
     */
    @RequestMapping("/welcome.do")
    public void welcomeHandler() {
    }

    /**
     * Custom handler for displaying vets.
     * Note that this handler returns a plain [email protected] ModelMap} object instead of
     * a ModelAndView, thus leveraging convention-based model attribute names.
     * It relies on the RequestToViewNameTranslator to determine the logical
     * view name based on the request URL: "/vets.do" -> "vets".
     * @return a ModelMap with the model attributes for the view
     */
    @RequestMapping("/vets.do")
    public ModelMap vetsHandler() {
        return new ModelMap(this.clinic.getVets());
    }

    /**
     * Custom handler for displaying an owner.
     * Note that this handler returns a plain [email protected] ModelMap} object instead of
     * a ModelAndView, thus leveraging convention-based model attribute names.
     * It relies on the RequestToViewNameTranslator to determine the logical
     * view name based on the request URL: "/owner.do" -> "owner".
     * @param ownerId the ID of the owner to display
     * @return a ModelMap with the model attributes for the view
     */
    @RequestMapping("/owner.do")
    public ModelMap ownerHandler(@RequestParam("ownerId") int ownerId) {
        return new ModelMap(this.clinic.loadOwner(ownerId));
    }
}

16.11.3.1 Advanced @RequestMapping options

Ant-style path patterns are supported (e.g. "/myPath/*.do"). At the method level, relative paths (e.g. "edit.do") are supported within the primary mapping expressed at the type level.

The handler method names are taken into account for narrowing if no path was specified explicitly, according to the specified org.springframework.web.servlet.mvc.multiaction.MethodNameResolver (by default an org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver). Note that this only applies in case of ambiguous annotation mappings that do not specify a path mapping explicitly. In other words, the method name is only used for narrowing among a set of matching methods; it does not constitute a primary path mapping itself.

If you have a single default method (without explicit path mapping), then all requests without a more specific mapped method found will be dispatched to it. If you have multiple such default methods, then the method name will be taken into account for choosing between them.

Path mappings can be narrowed through parameter conditions: a sequence of "myParam=myValue" style expressions, with a request only mapped if each such parameter is found to have the given value. "myParam" style expressions are also supported, with such parameters having to be present in the request (allowed to have any value). Finally, "!myParam" style expressions indicate that the specified parameter is not supposed to be present in the request.

16.11.4 Supported handler method arguments and return types

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 (Servlet API). You may choose any specific request/response type, e.g. ServletRequest / HttpServletRequest.

  • Session object (Servlet API): of type HttpSession. An argument of this type will enforce the presence of a corresponding session. As a consequence, such an argument will never be null. Note that session access may not be thread-safe, in particular in a Servlet environment: Consider switching the AnnotationMethodHandlerAdapter's "synchronizeOnSession" flag to "true" if multiple requests are allowed to access a session concurrently.

  • 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 (determined by the most specific locale resolver available, i.e. the configured LocaleResolver in a Servlet 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 Servlet API.

  • java.io.OutputStream / java.io.Writer for generating the response's content. This will be the raw OutputStream/Writer as exposed by the Servlet API.

  • @RequestParam annotated parameters for access to specific Servlet 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 (by writing the response content directly, declaring an argument of type ServletResponse / HttpServletResponse for that purpose) or if the view name is supposed to be implicitly determined through a RequestToViewNameTranslator (not declaring a response argument in the handler method signature).

  • 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.

16.11.5 Binding request parameters to method parameters with @RequestParam

The @RequestParam annotation is used to bind request parameters to a method parameter in your controller.

The following code snippet from the PetClinic sample application shows the usage:

@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {

    // ...

    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

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")).

16.11.6 Providing a link to data from the model with @ModelAttribute

@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("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {

    // ...

    @ModelAttribute("types")
    public Collection<PetType> populatePetTypes() {
        return this.clinic.getPetTypes();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String processSubmit(
            @ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {

        new PetValidator().validate(pet, result);
        if (result.hasErrors()) {
            return "petForm";
        }
        else {
            this.clinic.storePet(pet);
            status.setComplete();
            return "redirect:owner.do?ownerId=" + pet.getOwner().getId();
        }
    }
}

16.11.7 Specifying attributes to store in a Session with @SessionAttributes

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("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

16.11.8 Mapping cookie values with the @CookieValue annotation

The @CookieValue annotation allows a method parameter to be bound to the value of an HTTP cookie.

Let us consider that the following cookie has been received with an http request:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

The following code sample allows you to easily get the value of the "JSESSIONID"cookie:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie)  {

  //...

}

This annotation is supported for annotated handler methods in Servlet and Portlet environments.

16.11.9 Mapping request header attributes with the @RequestHeader annotation

The @RequestHeader annotation allows a method parameter to be bound to a request header.

Here is a request header sample:

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

The following code sample allows you to easily get the value of the "Accept-Encoding" and "Keep-Alive" headers:

@RequestMapping("/displayHeaderInfo.do")
  public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding, 
                                @RequestHeader("Keep-Alive") long keepAlive)  {

  //...

}

This annotation is supported for annotated handler methods in Servlet and Portlet environments.

16.11.10 Customizing WebDataBinder initialization

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.

16.11.10.1 Customizing data binding with @InitBinder

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));
    }

    // ...
}

16.11.10.2 Configuring a custom WebBindingInitializer

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.

The following example from the PetClinic application shows a configuration using a custom implementation of the WebBindingInitializer interface, org.springframework.samples.petclinic.web.ClinicBindingInitializer, which configures PropertyEditors required by several of the PetClinic controllers.

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="cacheSeconds" value="0" />
    <property name="webBindingInitializer">
        <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer" />
    </property>
</bean>