The Spring Bean Validation Framework, which is part of the Spring Modules project, allows you to perform validation declaratively using Java annotations. I've always liked the declarative approach, which we saw for instance in Commons Validator, but annotation-based validation is especially convenient.
JSR 303 (Bean Validation) specifies some standards around bean validation, though the Spring Bean Validation Framework does not adopt those standards. The Hibernate Validator project, on the other hand, aims to provide an implementation of the emerging JSR 303 standard.
While it very well could be subpar Googling skills on my part, there doesn't seem to be much detailed how-to information out there on actually using the Bean Validation Framework. Hence this article.
I'm using Spring 2.5.x (specifically, Spring 2.5.5) and Spring Modules 0.9. I assume that you already know Spring and Spring WebMVC in particular.
If you want to download the code, you can do so here:
You'll have to download the dependencies separately though.
Here's what you'll need (again, I'm using Spring 2.5.x and Spring Modules 0.9):
commons-collections.jarcommons-lang.jarcommons-logging.jarspring.jarspring-modules-validation.jarspring-webmvc.jarI'm going to do things a little differently than I normally do, and start with the Java first. We're going to build a very simple "Contact Us" form of the sort that you might use to ask a question, complain about lousy service, or whatever. Since we're just showing how validation works, I've left out the service and persistence tiers. We're going to do everything with a form bean and a controller.
Here's the form bean:
contact.UserMessage
package contact;
import org.springmodules.validation.bean.conf.loader.annotation.handler.Email;
import org.springmodules.validation.bean.conf.loader.annotation.handler.Length;
import org.springmodules.validation.bean.conf.loader.annotation.handler.NotBlank;
public final class UserMessage {
@NotBlank
@Length(max = 80)
private String name;
@NotBlank
@Email
@Length(max = 80)
private String email;
@NotBlank
@Length(max = 4000)
private String text;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
The bean itself is pretty uninteresting—I have field for the user's name, e-mail address, and the message text. But the cool part is that I've included annotations that specify validation constraints. It's probably self-explanatory, but I've specified that none of the fields is allowed to be blank, and I've also specified the maximum lengths for each. (You can also specify minimum lengths, which one could use instead of @NotBlank, but I'm using @NotBlank instead for a reason I'll explain in just a bit.) Finally, I've specified that email needs to be a valid e-mail address. It's that simple!
Here are the rest of the validation rules you can use.
Now here's the Spring MVC controller, which I've implemented as a POJO controller:
contact.ContactController
package contact;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public final class ContactController {
@Autowired
private Validator validator;
public void setValidator(Validator validator) {
this.validator = validator;
}
@RequestMapping(value = "/form", method = RequestMethod.GET)
public ModelMap get() {
// Because we're not specifying a logical view name, the
// DispatcherServlet's DefaultRequestToViewNameTranslator kicks in.
return new ModelMap("userMessage", new UserMessage());
}
@RequestMapping(value = "/form", method = RequestMethod.POST)
public String post(@ModelAttribute("userMessage") UserMessage userMsg,
BindingResult result) {
validator.validate(userMsg, result);
if (result.hasErrors()) { return "form"; }
// Use the redirect-after-post pattern to reduce double-submits.
return "redirect:thanks";
}
@RequestMapping("/thanks")
public void thanks() {
}
}
The Bean Validation Framework includes its own Validator implementation, called BeanValidator, and I'm making that injectable here. Also, note that we're going to autowire it in.
It may be that there's a standard, predefined interceptor to apply BeanValidator (as opposed to injecting the Validator into the controller), but if there is, I haven't seen it. I'd be interested to hear if you, gentle reader, know of one.
The noteworthy method here is the second post() method, which contains the validation code. I just call the standard validate() method, passing in the form bean and the BindingResult, and return the current logical view name if there's an error. That way the form shows the validation error messages, which we'll see below. If everything passes validation, I just redirect to a "thank you" page.
Now let's look at how we define the validation messages that the end user sees if his form submission fails validation.