LDSTechForumProjects

Spring MVC - Part 3

This lesson continues the training begun in Introduction to Spring MVC and Spring MVC - Part 2.

Notes

Before working on the lab content, there are a few things you'll need:

1. An IDE that you can use with Stack projects. If you are using the LDSTech IDE, you'll need version 1.2.1 or later. You can download the latest version of the LDSTech IDE.

2. Some sort of HTTP client that you can use to make HTTP requests to the application we are building. You'll need to be able to do POSTs and set headers, so a browser by itself won't be sufficient. Probably the best option is to use a browser plugin. I've found two that seem to work reasonably well for Firefox and Chrome:

Outline

  • Data Binding
    • Bean Properties
    • ConversionService
    • Pre-configured Converters
    • Adding new Converters
    • Conversion Errors
    • Customizing Error messages
  • Spring's form tag library
    • The form tag
    • The input tag
    • The checkbox tag
    • The password tag
    • The select tag
    • The option tag
    • The hidden tag
    • The errors tag
  • Validation
    • Validator Interface
    • JSR-303 validators
    • @Valid annotation

Slides

The slides for this training can be found here: Media:SpringMvcPart3.pptx

Presentation, Part 1

Get Adobe Flash player

Lab 1

In this lab, we'll create our model object that we'll be binding to and validating. We'll first bind to it by using request parameters, then create a simple form to do the same. Finally, we'll customize the conversion error messages.

First, we need to create a Stack Project that uses Spring MVC. Don't use a project from a previous training.

1. Create a project with Java Stack Starter, changing the following options:

  1. Give it the name mvc-training (you can give it a different name, but you'll need to adjust future steps accordingly)
  2. Security tab: Uncheck 'Use LDS Account Authentication'
  3. Service Layer tab: Uncheck 'Use a database?'

2. Import your new project into your IDE of choice and associate it with a Tomcat server. If you are unsure about how to do this, see our guides for doing so in Eclipse, IntelliJ, or NetBeans.

3. Now we need to create our model, the Person object.

package org.lds.model;

public class Person {

	private String name;
	
	private int age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
}

4. We'll need a controller, PersonController setup. Annotate it with @Controller so Spring knows about it.

package org.lds.view;

import org.springframework.stereotype.Controller;

@Controller
public class PersonController {
	
}

5. Add a method to PersonController that takes a Person object and returns the values. Map it to /person/echo

@RequestMapping("/person/echo")
public @ResponseBody String echoPerson(Person person) {
	return String.format("Name: %s, Age: %d", person.getName(), person.getAge());
}

6. Now use an HTTP Client (or just your browser) to call that method and pass in name and age parameters. (ie, http://localhost: 8080/mvc-training/person/echo?name=Spencer&age=5).

You should see plain text that shows the age and name entered.

7. Now let's make a simple html form to do the same thing. First, add a JSP (add.jsp) that has a form for the necessary person attributes and save it in src/main/webapp/WEB-INF/views/person/:

<html>
<body>

<form method="post" action="${pageContext['request'].contextPath}/person/echo">
	Name: <input type="text" name="name" /><br /><br />
	Age: <input type="text" name="age" /><br /><br />
	<input type="submit" value="Add" />
</form>

</body>
</html>

8. Now we just need a simple controller to display the form:

@RequestMapping("/person/add")
public String addPerson() {
	return "person/add";
}

9. Now open the form in your browser (http://localhost: 8080/mvc-training/person/add) and enter your information. You should see the same response you saw in step 7.

10. Try entering something like 'asdf' into the age field. What happens?

11. Let's try to give a better error message. Start by making it so the controller knows about errors and displays them nicely.

@Autowired
private MessageSource messageSource;

@RequestMapping("/person/echo")
public @ResponseBody String echoPerson(Person person, Errors errors, Locale locale) {
	if(errors.hasErrors()) {
		List<String> errorMessages = new ArrayList<String>();
		for(ObjectError e : errors.getAllErrors()) {
			errorMessages.add(messageSource.getMessage(e, locale));
		}
		return StringUtils.join(errorMessages, "\n");
	}
	return String.format("Name: %s, Age: %d", person.getName(), person.getAge());
}

A few things about this snippet of code:

  • First, we have to inject a MessageSource so that we can lookup internationalized error messages for a given error.
  • We add the Locale as one of the method parameters so that we can get the current users locale.
  • We get the error messages by passing our object to the MessageSource.

Submit the form again - what do you get?

12. Let's finish by making the error message easier to read. Open up messages.properties (in src/main/bundles) and add a property for dealing with int conversion errors.

typeMismatch.int={0} should be a numeric value.

Try submitting the form one last time - you should get a friendlier error message.

Lab 1, Solution

Get Adobe Flash player

Presentation, Part 2

Get Adobe Flash player

Lab 2

In this lab, we'll rewrite our simple form from Lab 1 to use Spring's form taglib. We'll also show conversion errors when they happen, and we'll allow the user to specify which languages they like to use.

1. First, let's make a small change to our form controller to pass in a blank Person object that we can use to bind our Spring tags to.

@RequestMapping("/person/add")
public String addPerson(Model model) {
	model.addAttribute("person", new Person());
	return "person/add";
}

2. Now we need to make some changes to our JSP (add.jsp). First, we need to tell it about the Spring taglibs.

<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

Next, we need to update our form to use Spring's tags.

<html>
<body>

<form:form commandName="person" method="post" action="${pageContext['request'].contextPath}/person/echo">
	Name: <form:input path="name"/><br /><br />
	Age: <form:input path="age"/><br /><br />
	<input type="submit" value="Add" />
</form:form>

</body>
</html>

3. Try viewing and submitting the form again - it should behave as it did before.

4. Now let's make it so conversion errors show up on this page. First, we'll need to make it so the echoPerson() handler returns a view instead of a response body. If there are errors, we'll simply redisplay the add page to show the user any errors that occurred and give them a chance to fix them and resubmit the form. If there are no errors, we'll show a view page that simply echos back what they added.

@RequestMapping("/person/echo")
public String echoPerson(Person person, Errors errors) {
	if(errors.hasErrors()) {
		return "person/add";
	}
	return "person/view";
}

And here is what our view page (src/main/webapp/WEB-INF/views/person/view.jsp) looks like:

<html>
	<body>
		Name: ${person.name}<br />
		Age: ${person.age}
	</body>
</html>

5. Now let's modify our add page to actually show the error messages. For the sake of thoroughness, we'll show all errors at the top and then show inline messages for each field as well. We'll also add in a simple CSS class to make the error messages stand out a little more.

<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<style>
.error {
	color: red;
}
</style>
</head>
<body>
<form:form commandName="person" method="post" action="${pageContext['request'].contextPath}/person/echo">
	<form:errors path="*" cssClass="error"/>
	<br /><br />
	Name: <form:input path="name"/><br />
	<form:errors path="name" cssClass="error"/><br />
	Age: <form:input path="age"/><br />
	<form:errors path="age" cssClass="error"/><br />
	<input type="submit" value="Add" />
</form:form>

</body>
</html>

6. Test your error handling by adding some text into the age field. You should see error messages on the page now.

7. Finally, let's allow the user to specify which programming languages they like. Start by giving them a list to choose from and adding it to the model on the 'add' page:

private String[] languages = { "Java", "Scala", "C#", "Objective-C", "JavaScript" };

@RequestMapping("/person/add")
public String addPerson(Model model) {
	model.addAttribute("person", new Person());
	model.addAttribute("languages", languages);
	return "person/add";
}

8. Next, we'll need to add a field on Person to keep track of the user's favorite languages. Be sure to add a getter and setter for it.

private List<String> favoriteLanguages;

9. Now we need to add checkboxes for it on the 'add' page:

<form:checkboxes items="${languages}" path="favoriteLanguages"/>

10. And finally, let's show them on the 'view' page. We'll need to add the JSTL core taglib and iterate over the favoriteLanguages on the user to show them all:

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
	<body>
		Name: ${person.name}<br />
		Age: ${person.age}<br />
		Favorite Languages:
		<ul>
			<c:forEach items="${person.favoriteLanguages}" var="lang">
				<li>${lang}</li>
			</c:forEach>
		</ul>
	</body>
</html>

Extra Credit

If, after adding the language checkboxes, you supply a string for name and trigger a conversion error, you no longer get a nice message and instead get an error. Why is this? How would you fix it?

(If you don't know, don't worry, we'll fix it in the next section).

Lab 2, Solution

Get Adobe Flash player

Presentation, Part 3

Get Adobe Flash player

Lab 3

Our page is almost done, but there is one problem - We can supply some non-sensical values. For example, I can leave the name field blank. Or, I can enter my age as -3 or 3434, which are both probably invalid ages. Let's make it so the name has to be at least 3 characters long and the age has to be a value between 0 and 125.

1. Add a @Size annotation to name and set the minimum to 3.

@Size(min=3)
private String name;

2. Add @Min and @Max annotations to age, so that the minimum age is 0 and the maximum is 125.

@Min(0)
@Max(125)
private int age;

3. We need to make a couple of changes to our controller method (echo):

  • Add @Valid to the person attribute - this tells Spring MVC to validate it.
  • Add a Model attribute to the end of the method parameters.
  • Populate the languages attribute in the event of there being errors.

Your controller method should look something like this:

@RequestMapping("/person/echo")
public String echoPerson(@Valid Person person, Errors errors, Model model) {
	if(errors.hasErrors()) {
		model.addAttribute("languages", languages);
		return "person/add";
	}
	return "person/view";
}

4. Restart your server and try leaving the name blank and entering an invalid age.

5. That looks ok, but we'd like to make the error messages a little better. Let's customize all 3 of them using the 3 different techniques for customizing error messages:

  • For the name size, use the 'message' attribute of that annotation to supply a custom message.
  • For the age minimum, override the default message for @Min (javax.validation.constraints.Min.message) in messages.properties.
  • For the age maximum, specify a custom property. Define that property in messages.properties.

Your Person object should look something like this:

@Size(min=3, message="Your name should be at least 3 characters.")
private String name;

@Min(0)
@Max(value = 125, message = "{age.too.old}")
private int age;

Your messages.properties should look something like this:

age.too.old=You''re too old to be using this website!
javax.validation.constraints.Min.message=You aren''t born yet! {0} must be at least 0.

6. Try submitting the form with invalid data and make sure you see your custom error messages.

Lab 3, Solution

Get Adobe Flash player
This page was last modified on 12 July 2013, at 18:22.

Note: Content found in this wiki may not always reflect official Church information. See Terms of Use.