Spring MVC 3.0 and JSR 303 (aka javax.validation.*)
The new annotated validation spec (jsr 303) is pretty slick, especially when used along side Spring MVC 3.0, and when backed by ejb3 entities. I’m pretty impressed with how easily it integrates with Spring MVC’s framework, and with how seamlessly error messages are passed to the form taglibs so they show up in your web forms.
I know some of you might argue that the current validation framework might not address complex validations, but after giving Hibernate’s reference implementation documentation a look, it seems interdependent validations are at least possible through @Valid. Even if you have to come up with your own really weird validation for a particular field, jsr 303/hibernate offers a way to create your own custom annotation driven validations. For the remaining 95% of all the other web forms, you’re probably going to be alright if you use the pre-defined validations offered by jsr 303. So lets take a look at an example entity, marked up with some validation annotations:
Example ejb3 entity bean
import javax.persistence.*;
@Entity
@Table(name="tb_inventory")
public class InventoryItem implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
protected int id;
@NotNull
protected BigDecimal price = new BigDecimal("0.00");
@NotEmpty(message = "Name is a required field")
protected String name;
@Min(100)
protected int minimumPrice;
@Email
private String mail;
@Pattern(regexp="[a-z]+")
private String lowerCaseName;
@Future
private Date futureDate;
@AssertTrue
private boolean mustBeTrue;
@OneToMany
@JoinTable(name="tb_order_items_to_images")
@Valid
protected List<InventoryImage> images = new ArrayList<InventoryImage>();
// setters/getters here...
}
Lets examine the annotations:
@NotNull
@NotNull flags the annotated field as valid only if it has a value assigned. A String with a null value will fail, while a String with a “” value will pass. Since the “message” parameter is not defined, the error message will default to whatever the validation package ships with. In this case, I think the message will read “name is a required field”.
@NotEmpty(message = “Name is a required field”)
@NotEmpty flags the annotated field as valid if the field is both not null and not a value of “”. Since the “message” parameter is defined for this annotation, the param value will replace the default message passed into spring mvc’s error framework.
@Min(100)
@Min flags the field as valid only if it has a value equal to or higher than the value in the parens. The contrary to this is @Max, which will flag as valid values lower than the value in the parens.
@Email flags the field as valid only if the field is a valid email.
@Pattern(regexp=”[a-z]+”)
@Pattern will flag the field as valid only if the string matches the regular expression passed in as the parameter. In this case it will only pass if the string is made up only of lowercase letters.
@Future
@Future will flag as valid only if the date annotated is in the future. The contrary to this is @Past, which would be valid only if the date has already passed.
@AssertTrue
@AssertTrue will flag as valid if the annotated boolean resolves to true. The contrary to this is @AssertFalse, which will flag as valid only if the boolean resolves to false.
@Valid
@Valid will validate only if the complex object annotated validates as true. Lets say in this case that InventoryImage has two validation annotated fields; if any InventoryImage fails either of those two fields, then the enclosing InventoryItem will fail validation because of the @Valid annotation. This is how compelx cross object validations are supported, other than defining your own.
Now that we’ve annotated our bean, we’ll need to hook it into a Spring MVC controller.
The Spring MVC Controller
package com.faceroller.web;
@Controller
@RequestMapping("/inventory")
public class InventoryController {
private static final Log log = LogFactory.getLog(InventoryController.class);
/**
* initialize the form
*
*/
@RequestMapping("/add/setup.go")
public ModelAndView addInventorySetup(InventoryItem item){
log.info("setup add inventory");
return new ModelAndView("/inventory/add.jsp", "item", item);
}
/**
* process the form
*
*/
@RequestMapping(value="/add/meta.go", method=RequestMethod.POST)
public String processInventoryMeta(
@ModelAttribute("item") @Valid InventoryItem item,
BindingResult result) {
log.info("process add inventory");
if (result.hasErrors()) {
return "/inventory/add.jsp";
}
InventoryService service = ServiceLocator.getInventoryService();
service.addInventoryItem(item);
return "redirect:/add/images/"+item.getId();
}
/**
* forward to whatever page you want
*
*/
@RequestMapping("/browse/item/{itemId}")
public ModelAndView getInventoryItem(@PathVariable int itemId){
log.info("getting item");
InventoryService service = ServiceLocator.getInventoryService();
InventoryItem item = service.getInventoryItemById(itemId);
return new ModelAndView("/inventory/browse.item.jsp", "item", item);
}
}
Pay special attention to
@RequestMapping(value = "/add/meta.go", method=RequestMethod.POST)
public String processInventoryMeta(
@ModelAttribute("item") @Valid InventoryItem item,
BindingResult result)
You’ll notice @Valid marked up right before the InventoryItem item bean parameter. This is the annotation that does all the validation magic for us. There is no need to implement a custom validator factory, as spring mvc’s framework would normally require. If the bean fails validation, BindingResult result will be prepopulated with all corresponding JSR 303 validation errors. The catch is you have to add the @ModelAttribute(“item”) annotation to the signature, otherwise the form bean in the jsp will not have access to all the error messages passed along by the validations.
The jsp code
<form:form method="post" commandName="item" action="/process/form"> <table width="100%" border="0"> <tr><td colspan="3" class="bottomPadding"> <span class="secionHeader">Add item to inventory</span> </td></tr> <tr><td class="labelColumn" width="100"> Price </td><td width="100"> <form:input path="price"/> </td><td> <form:errors path="price" cssClass="error"/> </td></tr> <tr><td class="labelColumn"> Name </td><td> <form:input path="name"/> </td><td> <form:errors path="name" cssClass="error"/> </td></tr> </table> </form:form>
This is just a simple form, nothing new here, but I’m including for completeness. The Spring MVC framework will correctly populate the form tags with any bean errors should the form fail validation.
Getting started
Download the jsr 303 reference implementation jars from SourceForge, via Hibernate’s download page. You’ll need to add the main, Hibernate validator jar (currently hibernate-validator-4.0.2.GA.jar as of 2/8/2009) and the included jars in the release jar’s lib directory to your application’s classpath if they’re not already there (if you’re on jboss 5.1, probably at least validation-api-1.0.0.GA.jar, maybe more). The Hibernate reference implementation release also includes the jar files required to run in a jdk 5 runtime, include those if you’re not running on jdk 6. Download Spring MVC from Spring’s download page, its part of the Spring 3.0 release. From what I can tell Spring MVC requires the following jars in your classpath:
- org.springframework.asm
- org.springframework.beans
- org.springframework.context
- org.springframework.context-support
- org.springframework.core
- org.springframework.expression
- org.springframework.web
- org.springframework.web.servlet
If you can get past all the classpath dependencies, you will be justly rewarded with a simple to use validation framework for your web application.
Resources
Hibernate 4.x Validation reference implementation
Spring MVC 3.0 documentation
In: Java · Tagged with: Java, jsr 303, mvc, spring, validation, web tier