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.
- It does not have any interface or base class requirements.
- It allows any number of request handling methods.
- 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 @RequestMappingfor 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.
Community commentsWatch Thread
Using Spring @MVC for REST services by Terence Ingram Posted Aug 06, 2008 05:23
Re: Using Spring @MVC for REST services by Ray Krueger Posted Aug 06, 2008 06:31
Re: Using Spring @MVC for REST services by Dmitriy Kopylenko Posted Aug 07, 2008 09:22
Re: Using Spring @MVC for REST services by Geoffrey Wiseman Posted Aug 13, 2008 09:56
Re: Using Spring @MVC for REST services by Gavin Terrill Posted Aug 14, 2008 02:01
Re: Using Spring @MVC for REST services by Juergen Hoeller Posted Aug 18, 2008 06:26
Re: Using Spring @MVC for REST services by Terence Ingram Posted Aug 24, 2008 05:40
Re: Using Spring @MVC for REST services by Stefan Tilkov Posted Aug 14, 2008 02:43
Re: Using Spring @MVC for REST services by David Tkaczyk Posted Aug 15, 2008 05:10
Re: Using Spring @MVC for REST services by Geoffrey Wiseman Posted Aug 15, 2008 09:04
Link needed by Colin Sampaleanu Posted Aug 13, 2008 11:05
Re: Link needed by Colin Sampaleanu Posted Aug 13, 2008 11:12
Re: Link needed by Geoffrey Wiseman Posted Aug 14, 2008 12:23
Can an interceptor work with an annotation based form controller? by Vernon W Posted Jul 28, 2009 02:16
Part 3 of the Series? by Yohan Liyanage Posted Sep 09, 2012 05:00
Using Spring @MVC for REST servicesAug 06, 2008 05:23 by
Terence Ingram
I have been using Spring MVC for my rest services. I have been very happy with using it.
I chose this as I was using hibernate and I really like the @Transactional annotation :) When @MVC was released I easily upgraded my old services. I am quite happy with it.
But I am hoping that the Spring crew get more serious about REST and enhance @MVC to fully support REST. I have some suggestions that would make it easier: enhance the @RequestMapping annotation to be more like the @Path annotation as used in javax.ws.rs.Path. What I am looking for is the ability to break up the URL easily i.e. @RequestMapping("/accounts/{id}/details") where I can then reference id as an instance variable in my code having the value populated. Also automatic WADL support would be nice. This is not essential but would save me the hassle of doing it by hand.
I chose this as I was using hibernate and I really like the @Transactional annotation :) When @MVC was released I easily upgraded my old services. I am quite happy with it.
But I am hoping that the Spring crew get more serious about REST and enhance @MVC to fully support REST. I have some suggestions that would make it easier: enhance the @RequestMapping annotation to be more like the @Path annotation as used in javax.ws.rs.Path. What I am looking for is the ability to break up the URL easily i.e. @RequestMapping("/accounts/{id}/details") where I can then reference id as an instance variable in my code having the value populated. Also automatic WADL support would be nice. This is not essential but would save me the hassle of doing it by hand.
Re: Using Spring @MVC for REST servicesAug 06, 2008 06:31 by
Ray Krueger
Yes! I completely agree with Terence above. The @MVC project is simply incomplete without support for REST style urls EX: @RequestMapping("/accounts/{id}"). Continuing to rely solely on request parameter binding is not what the world wants to do these days. The JAXRS stuff is a kind of a pain in the ass to use (especially if you like Spring) and this is where Spring could really help out :)
Re: Using Spring @MVC for REST servicesAug 07, 2008 09:22 by
Dmitriy Kopylenko
"True" REST support should be upcoming in Spring 3: www.springify.com/archives/16
Re: Using Spring @MVC for REST servicesAug 13, 2008 09:56 by
Geoffrey Wiseman
Yes, looking forward to seeing how this comes about -- getting good RESTful URLs into a Spring MVC application will be great, although it'd also be nice to see some collaboration with JAX-RS if you're doing RESTful web services in addition to RESTful URLs in your web application.
This was one of the first questions that came to my mind looking over the parameter handling and url mapping in Spring MVC as well.
This was one of the first questions that came to my mind looking over the parameter handling and url mapping in Spring MVC as well.
Link neededAug 13, 2008 11:05 by
Colin Sampaleanu
This post needs a link to the article right in the body of the post. Also, the link to Mark Fisher's original article does not seem to work (for me, anyway).
Regards,
Colin
Regards,
Colin
Re: Link neededAug 13, 2008 11:12 by
Colin Sampaleanu
Sorry, I am talking about the news post meant to announce the article. I just realized the article itself also has these comments hanging off it.
Re: Using Spring @MVC for REST servicesAug 14, 2008 02:01 by
Gavin Terrill
Looks like SpringSource might be backing away from JAX-RS. Check out the comment from Juergen on Jan 7.
Re: Using Spring @MVC for REST servicesAug 14, 2008 02:43 by
Stefan Tilkov
The JAXRS stuff is a kind of a pain in the ass to use
Could you elaborate? What exactly do you consider to be a PITA?
Re: Using Spring @MVC for REST servicesAug 15, 2008 05:10 by
David Tkaczyk
Could you elaborate as to why find jax-rs to be a "pain in the ass" to use. I have had nothing but good luck with it and find it extremely easy to use. Maybe I haven't used as advanced features, but I'm curious as to why you think so. Thanks.
Re: Using Spring @MVC for REST servicesAug 15, 2008 09:04 by
Geoffrey Wiseman
I have had nothing but good luck with it and find it extremely easy to use. Maybe I haven't used as advanced features, but I'm curious as to why you think so. Thanks.
Out of curiosity, what JAX-RS implementation are you using? I've used Restlet in its pre-JAX-RS form and a few other approaches for rest, but still haven't done more than experiment with JAX-RS implementations, so I'm happy to hear feedback "from the field" as it were.
Re: Using Spring @MVC for REST servicesAug 18, 2008 06:26 by
Juergen Hoeller
Our core strategy is to bring REST to the Spring @MVC world in as natural a fashion as possible. This involves URI template support for Spring's @RequestMapping facility, a dedicated parameter-level @PathParam annotation for parameter extraction based on such a template, etc. This is supposed to be a natural next step after Spring MVC 2.5, preserving all common Spring MVC idioms; you could see it as "completing @MVC".
We'll also provide a RestTemplate class for client-side HTTP access to REST-style services.
Currently, the first Spring Framework 3.0 milestone - including a first cut of the dedicated REST support - is scheduled for mid September. We are aiming for GA fairly soon, with 3.0 RC1 being scheduled for December already.
With respect to JAX-RS: The JSR 311 spec essentially defines a dedicated REST resource endpoint model there, not being a great fit for REST-enabling an existing MVC endpoint model. This is why JAX-RS is not the primary point of our REST strategy.
We're considering integration with JAX-RS on a separate basis - separate from Spring MVC's own endpoint model -, possibly supporting the use of Jersey (the JAX-RS RI) with Spring-style beans in a Spring web application context. This might make Spring 3.0 as well, depending on the finalization of JSR 311 and Jersey in time for Spring 3.0 RC1. Otherwise it would be a candidate for Spring 3.1.
Juergen
We'll also provide a RestTemplate class for client-side HTTP access to REST-style services.
Currently, the first Spring Framework 3.0 milestone - including a first cut of the dedicated REST support - is scheduled for mid September. We are aiming for GA fairly soon, with 3.0 RC1 being scheduled for December already.
With respect to JAX-RS: The JSR 311 spec essentially defines a dedicated REST resource endpoint model there, not being a great fit for REST-enabling an existing MVC endpoint model. This is why JAX-RS is not the primary point of our REST strategy.
We're considering integration with JAX-RS on a separate basis - separate from Spring MVC's own endpoint model -, possibly supporting the use of Jersey (the JAX-RS RI) with Spring-style beans in a Spring web application context. This might make Spring 3.0 as well, depending on the finalization of JSR 311 and Jersey in time for Spring 3.0 RC1. Otherwise it would be a candidate for Spring 3.1.
Juergen
Re: Using Spring @MVC for REST servicesAug 24, 2008 05:40 by
Terence Ingram
It sounds like Spring 3.0 will have what I am after. Integration with Jersey would be nice. We have been using both Jersey and Spring currently in our REST services. It would be nice to have them officially supported together.
Can an interceptor work with an annotation based form controller?Jul 28, 2009 02:16 by
Vernon W
I wish that I read this article before I started working on the Spring 2.5 including the web layer. When I am working on coding for Spring 2.5 web layer, I can't figure out how to configure an interceptor with a form controller as what it can be done with the XML configuration approach. I have made non-form controllers work with an interceptor, but not a form controller. I have seen some examples of one interceptor for all controller. I only need to an interceptor for selected form controllers.
Does anyone have an information in this regard?
Does anyone have an information in this regard?
Tell us what you think