BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Spring 2.5: New Features in Spring MVC

Spring 2.5: New Features in Spring MVC

This item in japanese

Lire ce contenu en français

Since its inception the Spring Framework has focused on providing powerful, yet non-invasive solutions to complex problems. Spring 2.0 introduced custom namespaces as a way of reducing XML-based configuration. They have since taken root in the core Spring framework (the aop, context, jee, jms, lang, tx, and util namespaces), in Spring Portfolio projects (e.g. Spring Security), and in non-Spring projects (e.g. CXF).

Spring 2.5 rolled out a comprehensive set of annotations as an alternative to XML-based configuration. Annotations can be used for auto-discovery of Spring-managed objects, dependency injection, lifecycle methods, Web layer configuration, and unit/integration testing.

This article is the second part of a three-part series exploring annotations introduced in Spring 2.5. It covers annotations support in the Web layer. The final article will highlight additional features available for integration and testing.

Part 1 of the series demonstrated how Java annotations can be used as an alternative to XML for configuring Spring-managed objects and for dependency injection. Here is once again an example:

@Controller
public class ClinicController {

private final Clinic clinic;

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

@Controller indicates ClinicController is a Web layer component. @Autowired requests an instance of a Clinic to be dependency injected. This example requires only a small amount of XML to enable the recognition of both annotations and to limit the scope of component scanning:

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

This is great news for the Web layer where Spring XML configuration tends to be more verbose and perhaps even of less value than in the layers below. Controllers hold a multitude of properties such as view names, form object names, and validator types, which is more about configuration and less about dependency injection. There are ways to manage such configuration effectively through bean definition inheritance or by avoiding configuration for properties that don't change very often. However in my experience many developers don't do that and the result is more XML than is necessary. Thus @Controller and @Autowired can have a very positive effect on Web layer configuration.

Continuing this discussion in part 2 of the series we take a tour of Spring 2.5 annotations for the Web layer. These annotations are informally known as @MVC, which is a reference to Spring MVC and Spring Portlet MVC. Indeed most of the functionality discussed in this article applies to both.

From Controller to @Controller

In contrast to the annotations discussed in part 1, @MVC is more than just an alternative form of configuration. Consider this well-known signature of a Spring MVC Controller:

public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse
response) throws Exception;
}

All Spring MVC controllers either implement Controller directly or extend from one of the available base class implementations such as AbstractController, SimpleFormController, MultiActionController, or AbstractWizardFormController. It is this interface that allows Spring MVC's DispatcherServlet to treat all of the above as "handlers" and to invoke them with the help of an adapter called SimpleControllerHandlerAdapter.

@MVC changes this programming model in 3 significant ways.

  1. It does not have any interface or base class requirements.
  2. It allows any number of request handling methods.
  3. It allows a high degree of flexibility in the method's signature.

Given those three it's fair to say @MVC is not merely an alternative. It's the next step in the evolution of Spring MVC's controller technology.

The DispatcherServlet invokes annotated controllers with the help of an adapter called AnnotationMethodHandlerAdapter. It is this adapter that does most of the work to support the annotations discussed hereafter. It is also this adapter that effectively replaces the need for base class controllers.

Introducing @RequestMapping

We begin with a controller that looks similar to a traditional Spring MVC Controller:

@Controller
public class AccountsController {

private AccountRepository accountRepository;

@Autowired
public AccountsController(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}

@RequestMapping("/accounts/show")
public ModelAndView show(HttpServletRequest request,
HttpServletResponse response) throws Exception {
String number = ServletRequestUtils.getStringParameter(request, "number");
ModelAndView mav = new ModelAndView("/WEB-INF/views/accounts/show.jsp");
mav.addObject("account", accountRepository.findAccount(number));
return mav;
}
}

What's different here is this controller does not extend the Controller interface, and it also uses the @RequestMapping annotation to indicate show() is a request handling method mapped to the URI path "/accounts/show". The rest of the code is typical for a Spring MVC controller.

We will come back to @RequestMapping after we have fully converted the above method to @MVC but before moving on it's worth mentioning the above request mapping URI also matches to URI paths with any extension such as:

/accounts/show.htm
/accounts/show.xls
/accounts/show.pdf
...

Flexible Request Handling Method Signatures

We promised flexible method signatures. Let's go ahead and remove the response object from the input parameters and instead of returning a ModelAndView, we'll add a Map as an input parameter representing our model. Also, we'll return a String to indicate the view name to use when rendering the response:

@RequestMapping("/accounts/show")
public String show(HttpServletRequest request, Map<String, Object> model)
throws Exception {
String number = ServletRequestUtils.getStringParameter(request, "number");
model.put("account", accountRepository.findAccount(number));
return "/WEB-INF/views/accounts/show.jsp";
}

The Map input parameter is known as the "implicit" model and it is conveniently created for us before the method is invoked. Adding key-value pairs to it makes the data available for rendering in the view -- in this case the show.jsp page.

@MVC allows a number of types to be used as input parameters such as HttpServletRequest/HttpServletResponse, HttpSession, Locale, InputStream, OutputStream, File[] and others. They can be provided in any order. It also allows a number of return types such as ModelAndView, Map, String, and void among others. Examine the JavaDoc of @RequestMapping for a complete list of supported input and output parameter types.

One interesting case is what happens when a method does not specify a view (e.g. return type is void). In that case the convention is for the DispatcherServlet to re-use the path info of the request URI removing the leading slash and the extension. Let's change the return type to void:

@RequestMapping("/accounts/show")
public void show(HttpServletRequest request, Map<String, Object> model) throws Exception {
String number = ServletRequestUtils.getStringParameter(request, "number");
model.put("account", accountRepository.findAccount(number));
}

Given this request handling method and a request mapping of "/accounts/show" we can expect the DispatcherServlet to fall back on the default view name of "accounts/show" which - when combined with a suitable view resolver such as the one below - yields the same result as before:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>

Relying on conventions for view names is highly recommended because it helps to eliminate hard-coded view names from controllers. If you need to customize how the DispatcherServlet derives default view names, configure your own implementation of RequestToViewNameTranslator in the servlet context giving it the bean id "viewNameTranslator".

Extracting and Parsing Parameters with @RequestParam

Another feature of @MVC is its ability to extract and parse request parameters. Let's continue refactoring our method and add the @RequestParam annotation:

@RequestMapping("/accounts/show")
public void show(@RequestParam("number") String number, Map<String, Object> model) {
model.put("account", accountRepository.findAccount(number));
}

Here the @RequestParam annotation helps to extract a String parameter named "number" and to have it passed in as an input parameter. @RequestParam supports type conversion as well as required vs. optional parameters. For type conversion, all basic Java types are supported and you can extend that with custom PropertyEditors. Here are a few more examples including required and optional parameters:

@RequestParam(value="number", required=false) String number
@RequestParam("id") Long id
@RequestParam("balance") double balance
@RequestParam double amount

Notice how the last example does not provide an explicit parameter name. This would result in extracting a parameter called "amount" but only if the code is compiled with debug symbols. If the code was not compiled with debug symbols an IllegalStateException would be thrown because there is insufficient information to extract the parameter from the request. For this reason it's preferable to specify the parameter name explicitly.

@RequestMapping Continued

It is legitimate to place @RequestMapping on the class level and use it in conjunction with method-level @RequestMapping annotations to achieve an effect of narrowing down the choice. Here are some examples.

Class-level:

RequestMapping("/accounts/*")

Method-level:

@RequestMapping(value="delete", method=RequestMethod.POST)
@RequestMapping(value="index", method=RequestMethod.GET, params="type=checking")
@RequestMapping

The first method-level request mapping in combination with the class-level mapping matches to "/accounts/delete" where the HTTP method is POST. The second one adds a requirement for a request parameter named "type" and with a value "checking" to be present in the request. The third method does not specify a path at all. This method matches to all HTTP methods and its method name will be used if necessary. Let's change our method to rely on the method name resolution as follows:

@Controller
@RequestMapping("/accounts/*")
public class AccountsController {

@RequestMapping(method=RequestMethod.GET)
public void show(@RequestParam("number") String number, Map<String, Object> model)
{
model.put("account", accountRepository.findAccount(number));
}
...

The method matches to requests for "/accounts/show" based on a class-level @RequestMapping of "/accounts/*" and a method name of "show".

Removing Class-Level Request Mappings

One frequent objection to annotations in the Web layer is the fact that URI paths are embedded in the source code. This is easily remedied by using an XML-configured strategy for matching URI paths to controller classes and @RequestMapping annotations for method-level mapping only.

We will configure a ControllerClassNameHandlerMapping, which maps URI paths to controllers using a convention that relies on a controller's class name:

<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>

Now requests for "/accounts/*" are matched to the AccountsController. This works well in combination with method-level @RequestMapping annotations, which complete the above mapping by adding the method name to it. Furthermore since our method is not returning a view name we're now using a convention that matches the class name, the method name, the URI path, and the view name.

This is what the @Controller looks like after it has been fully converted to @MVC:

@Controller
public class AccountsController {

private AccountRepository accountRepository;

@Autowired
public AccountsController(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}

@RequestMapping(method=RequestMethod.GET)
public void show(@RequestParam("number") String number, Map<String, Object> model)
{
model.put("account", accountRepository.findAccount(number));
}
...

And the supporting XML:

<context:component-scan base-package="com.abc.accounts"/>

<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>

As you can see there is minimal XML, no URI paths embedded in annotations, no explicit view names, the request handling method consists of a single line, the method signature matches precisely what we need, and additional request handling methods can be easily added. All of these benefits come without the need for a base class and without XML - at least none directly attributable to this controller.

Perhaps you can begin to see how effective this programming model can be.

@MVC Form Processing

A typical form processing scenario involves retrieving the object to be edited, presenting the data it holds in edit mode, allowing the user to submit, and finally validating and saving the changes. To assist with all of this, Spring MVC offers several features: a data binding mechanism for fully populating an object from request parameters, support for processing errors and for validation, a JSP form tag library, and base class controllers. With @MVC nothing changes except that the base class controller is no longer necessary because of the following annotations: @ModelAttribute, @InitBinder, and @SessionAttributes.

The @ModelAttribute Annotation

Take a look at these request handling method signatures:

@RequestMapping(method=RequestMethod.GET)
public Account setupForm() {
...
}

@RequestMapping(method=RequestMethod.POST)
public void onSubmit(Account account) {
...
}

They are perfectly valid request handling method signatures. The first method handles the initial HTTP GET. It prepares the data to be edited and returns an Account for use with Spring MVC's form tags. The second method handles the subsequent HTTP POST when the user is submitting the changes. It accepts an Account that is auto-populated from request parameters using Spring MVC's data binding mechanism. This is a very simple programming model.

The Account object holds the data to be edited. In Spring MVC terminology the Account is known as the form model object. This object must be made available to the form tags (as well as to the data binding mechanism) under some name. Here is an excerpt from a JSP page that's referring to a form model object named "account":

<form:form modelAttribute="account" method="post">
Account Number: <form:input path="number"/><form:errors path="number"/>
...
</form>

This JSP snippet will work well with the above method signatures even though we did not specify the name "account" anywhere. This is because @MVC uses the name of the returned object type to pick a default name. Hence an object of type Account by default results in a form model object named "account". If the default is not suitable, we can use @ModelAttribute to change its name as follows:

@RequestMapping(method=RequestMethod.GET)
public @ModelAttribute("account") SpecialAccount setupForm() {
...
}
@RequestMapping(method=RequestMethod.POST)
public void update(@ModelAttribute("account") SpecialAccount account) {
...
}

The @ModelAttribute can also be placed at method-level for a slightly different effect:

@ModelAttribute
public Account setupModelAttribute() {
...
}

Here setupModelAttribute() is not a request handling method. Instead it is a method used to prepare the form model object before any other request handling method is invoked. For those who are familiar, this is very similar to the SimpleFormController's formBackingObject() method.

Placing @ModelAttribute on a method is useful in a form processing scenario where we retrieve the form model object once during the initial GET and then a second time on the subsequent POST when we want data binding to overlay the existing Account object with the user's changes. Of course an alternative to retrieving the object twice would be to store it in the HTTP session between the two requests. That's what we will examine next.

Storing attributes with @SessionAttributes

The @SessionAttributes annotation can be used to specify either the names or the types of form model objects to be kept in the session between requests. Here are a couple of examples:

@Controller
@SessionAttributes("account")
public class AccountFormController {
...
}

@Controller
@SessionAttributes(types = Account.class)
public class AccountFormController {
...
}

With this annotation the AccountFormController stores a form model object named "account" (or in the second case, any form model object of type Account) in the HTTP session between the initial GET and the subsequent POST. The attribute should be removed from the session when the changes are persisted. We can do this with the help of a SessionStatus instance, which @MVC will pass if added to the method signature of the onSubmit method:

@RequestMapping(method=RequestMethod.POST)
public void onSubmit(Account account, SessionStatus sessionStatus) {
...
sessionStatus.setComplete(); // Clears @SessionAttributes
}

Customizing the DataBinder

Sometimes data binding requires customizations. For example we may need to specify required fields or register custom PropertyEditors for dates, currency amounts, and the like. With @MVC this is easy to do:

@InitBinder
public void initDataBinder(WebDataBinder binder) {
binder.setRequiredFields(new String[] {"number", "name"});
}

A method annotated with @InitBinder can access the DataBinder instance @MVC uses for binding request parameters. It allows us to make the customizations necessary for each controller.

Data Binding Results and Validation

Data binding may result in errors such as type conversion failures or missing fields. If any errors do occur, we would like to return to the edit form and allow the user to make corrections. For that we add a BindingResult object to the method signature immediately following the form model object. Here is an example:

@RequestMapping(method=RequestMethod.POST)
public ModelAndView onSubmit(Account account, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
ModelAndView mav = new ModelAndView();
mav.getModel().putAll(bindingResult.getModel());
return mav;
}
// Save the changes and redirect to the next view...
}

In case of errors we return to the view we came from adding attributes from the BindingResult to the model so that field-specific errors can be displayed back to the user. Notice that we do not specify an explicit view name. Instead we allow the DispatcherServlet to fall back on a default view name that will match the path info of the incoming URI.

Validation requires only one additional line to invoke the Validator object passing the BindingResult to it. This allows us to accumulate binding and validation errors in one place:

@RequestMapping(method=RequestMethod.POST)
public ModelAndView onSubmit(Account account, BindingResult bindingResult) {
accountValidator.validate(account, bindingResult);
if (bindingResult.hasErrors()) {
ModelAndView mav = new ModelAndView();
mav.getModel().putAll(bindingResult.getModel());
return mav;
}
// Save the changes and redirect to the next view...
}

This concludes our tour of Spring 2.5 annotations for the Web layer informally referred to as @MVC.

Summary

Annotations in the Web layer have proven to be very beneficial. Not only do they significantly reduce the amount of XML configuration, but they also enable an elegant, flexible, and simple programming model with full access to Spring MVC's controller technology. It is highly recommended to use convention-over-configuration features as well as a centralized handler mapping strategy for delegating requests to controllers in order to avoid embedding URI paths in source code or defining explicit references to view names.

Lastly, although not discussed in this article, it is worth mentioning a very important Spring MVC extension. The recently released Spring Web Flow version 2 adds features such as Spring MVC based JSF views, a Spring JavaScript library, and advanced state and navigation management supporting more advanced editing scenarios.

Rate this Article

Adoption
Style

BT