Home > Software Development > Practical Spring MVC Part 2: Forms

Practical Spring MVC Part 2: Forms

In this quick-paced, demo-driven series, I will take you on an fascinating tour of Spring MVC. Unlike “pet clinic” style demonstrations, I will make use of practical solutions to genuine-world issues in order to demonstrate the breadth of functionality offered by Spring MVC. This write-up is a ideal match for anyone looking for a fast overview of Spring MVC and its capabilities. Moreover, total code is included for those who wish to a stick to along closely and explore the ideas presented.

Introduction

If you have not already read Part 1: The Basics, I strongly recommend you do so now.  Most of the material covered in this part depends on concepts covered in the previous part.  In this part we will add the ability to post new articles as well as update existing ones.  We will even add the ability to upload images.

Edit

There are two parts to every form: a method that displays the form and a method that processes the form submission.  The first step is trivial to implement with the features presented so far:

@RequestMapping(value="articles/{id}/form", method=RequestMethod.GET)
@Transactional(readOnly=true)
public String viewUpdateForm(@PathVariable long id, ModelMap model) {
	Article article = service.load(id);
	model.put("article", article);
	return "articles/form.update";
}

Note: the REST guidelines do not specify how the form is to be retrieved.  This is because REST clients do not typically use forms.  However, the specification does state how related objects are to be retrieved.  As such, a common way to handle forms in a REST-style web application is to treat the form as a property of the parent object.  As such, the URL to access the edit form is /articles/ID/form.html, where ID is the ID of the article to be edited.

Since the form we used to edit articles will be nearly identical to the form we use to create new articles, I have abstracted the form into a separate file (form.include.ftl), which is included by form.update.ftl.  These two files demonstrate only the most basic features of Spring’s powerful form tag library, the complete details of which are beyond the scope of this presentation.  In a nutshell, Spring provide support for:

  • Binding object values to form controls,
  • Binding validation errors to the appropriate controls,
  • Binding objects to forms,
  • HTTP verbs not supported by browsers (put, delete) for proper REST support.

As mentioned before, while this example is written with Freemarker, Spring offers the same suite of tags for both JSP and Velocity.

Update

The second step is to process the form submission.  This is where things get very exciting!

@RequestMapping(value="articles/{id}", method=RequestMethod.PUT)
@Transactional
public String update(@PathVariable long id, @ModelAttribute("article") Article article, BindingResult result) {
	article.setId(id);
	
	validator.validate(article, result);
	if (result.hasErrors())
		return "articles/form.update";
	
	service.save(article);
	return "redirect:/articles/{id}.html";
}

First, notice that we are using the PUT request method.  This is specified by REST.  The URL for viewing an article and updating an article are the same with the exception of the HTTP verb (GET versus PUT).  Second, notice that the transaction is not read-only.  Obviously, we will be making changes to the database.

An Article parameter has been added to the parameter list.  When Spring encounters a command object in the parameter list, it will create a new instance and bind the values of the submitted form to that instance.  As such, we can expect the results of the form submission to be packaged nicely into this article instance.  Spring’s ability to bind request parameters to objects is incredibly powerful and fully extensible.  The subsequent BindingResult object allows us to catch any errors that arose during binding (e.g. type conversion errors).

Furthermore, the parameter has been marked @ModelAttribute.  Spring will automatically add this object to the model using the name supplied (i.e. “article”).

Spring offers transparent integration with Java Beans Validation.  All we have to do is wire a Validator into our controller:

@Autowired
private Validator validator;

We can use this validator to validate our article, placing any errors it finds into the result object.  If the outcome object contains any errors (either from binding or validation) we can re-present the kind.  Spring’s kind binding tags will automatically present translated error messages next to the suitable fields.

If there are no errors we save the post and redirect the user back to the article’s view.  Notice, we can carry out a redirect by returning a string that begins with “redirect:”.  Everything following will be treated as a URL and the browser will be redirected accordingly.  This prevents accidental type processing if the user presses refresh and is a very typical way of handling types safely.  Requests can also be forwarded by prefixing a URL with “forward:”.

All of this is wonderful, but, the validation portion is a little tedious.  In fact, Spring can be instructed to automatically validate a command object after it has been bound.  To do this, we mark the parameter @Valid:

@RequestMapping(value="articles/{id}", method=RequestMethod.PUT)
@Transactional
public String update(@PathVariable long id, @Valid @ModelAttribute("article") Article article, BindingResult result) {
	article.setId(id);
	
	if (result.hasErrors())
		return "articles/form.update";
	
	service.save(article);
	return "redirect:/articles/{id}.html";
}

We still have to check for errors and react accordingly.  However, we do save the boilerplate validation call and eliminate a direct dependency on the validator.

Create

For simple business cases, such as our blog, the processes of creating new entities and updating existing entities are nearly identical.  In more complex situations, there may be subtle differences.  As such, I highly recommend resisting the urge to combine the two processes.

@RequestMapping(value="articles/form", method=RequestMethod.GET)
@Transactional(readOnly=true)
public String viewInsertForm(ModelMap model) {
	model.put("article", new Article());
	return "articles/form.insert";
}

Here, I created a new article and put into the model.  However, I could also accomplish the same thing with the @Model Attribute:

@RequestMapping(value="articles/form", method=RequestMethod.GET)
@Transactional(readOnly=true)
public String viewInsertForm(@ModelAttribute("article") Article article) {
	return "articles/form.insert";
}

The advantage of this strategy is that the form could be prefilled by providing request parameters.  For example, a URL of /articles/form.html?title=Test will present the user with the new article form with the title field set to “Test”.

Insert

The code for processing the form is nearly identical to that of processing an update.

@RequestMapping(value="articles", method=RequestMethod.POST)
@Transactional
public String insert(@Valid @ModelAttribute("article") Article article, BindingResult result, ModelMap model) {
	if (result.hasErrors())
		return "articles/form.update";
	
	service.save(article);
	
	model.put("message", "inserted");
	return "redirect:/articles/" + article.getId() + ".html";
}

It is worth calling special attention to the redirect.  Notice that, after the article has been saved, we are able to use its new ID to create the appropriate URL.

In this instance, I put in additional value into the model: a message.  This is to demonstrate a feature of redirection in Spring.  Spring will automatically append any straightforward values found in the model to the URL throughout redirection.  This successfully creates a straightforward flash scope (complete help for flash scope is available in Spring 3.1).  The message will be persisted via the redirect and grow to be available to the view post approach.  This method can then add it back to the model and utilized as a flag in the presentation layer to explicitly inform the user that this article, which they are currently looking at, was successfully inserted/developed.

We could accomplish this by creating an additional optional request parameter called message in our view method.  Or, we could support message passing within our controller more generally by creating a model attribute method:

@ModelAttribute("message")
public String message(@RequestParam(required=false) String message) {
	return null;
}

Any method marked @ModelAttribute will be run before the request mappings and its results we placed in the model.  This facilitates easy population of the model with reference data.

Remove

Adding support for removing an article is now absolutely trivial.

@RequestMapping(value="articles/{id}", method=RequestMethod.DELETE)
@Transactional
public String remove(@PathVariable long id) {
	Article article = service.load(id);
	service.remove(article);
	return "redirect:/articles.html";
}

In fact, I have only included it for completeness.

View Image

Our blogging system supports the association of an image with each article.  As such, we need to add a method that will present the image for a given article.

@RequestMapping(value="articles/{id}.jpg", method=RequestMethod.GET)
@Transactional(readOnly=true)
public void viewImage(@PathVariable long id, OutputStream out) throws IOException {
	Article article = service.load(id);
	IOUtils.copy(service.loadImage(article), out);
}

Notice the RequestMapping.  The URL is identical to that for viewing article, with the exception that we have specified the file extension of.jpg.  If the request comes in for /articles/ID.jpg, the viewImage method will be executed.  All other requests for URLs of the pattern /articles/ID.*will be forwarded to the view method.  We will discuss some additional benefits of this in the next part.

Our method accepts an OutputStream.  Spring will provide us the OutputStream associated with the response.  Similarly, we could ask for the request’s InputStream, the HTTPServletRequest, and the HTTPServletResponse.  With the appropriate output stream, we can load the image and send it to the client.

Update Image

We also need to allow an image to be uploaded.  Spring supports multipart file uploads out-of-the-box.  Refer to the Spring configuration file for the one line configuration required.

@RequestMapping(value="articles/{id}/image", method={RequestMethod.POST, RequestMethod.PUT})
@Transactional
public String updateImage(@PathVariable long id, @RequestParam MultipartFile image) throws IOException {
	Article article = service.load(id);
	service.saveImage(article, image.getInputStream());
	return "redirect:/articles/" + article.getId() + ".html";
}

To accept the file we simply need to create a MultipartFile parameter, from which we can get an input stream to save the image.  We pass this stream to the ImageRepository, which subsequently create a scaled-down version of the image and saves it to the file system (interesting code you may wish to review).

Summary

In the second element we have demonstrated how incredibly easy Spring MVC makes type processing and file uploading.  This is only the tip of the iceberg.  Springs capability to bind request parameters intuitively and intelligently to objects brings incredible simplification and sort safety to the world of controllers.

In the subsequent write-up I will demonstrate the incredible energy of Spring MVC’s view resolver framework by extending our example application to support JSON, XML, PDF, and Excel export without having altering anything in our controller!

 

  1. No comments yet.