In this article, we do not detail about JSR 303, for working with JSR 303, please read SpringMVC 3 + JSR 303 validation first.
Why we need a custom constraint validator, of course there are too much reasons for this question. Here I will show you 2 reasons:
I use this util to get the value of a field in an object. This util is not important, you can use another one
When <form:errors> renders error messages on form, it will look in this file, for more information, please read Message interpolation.
Note: by default, the normal Hibernate Validator still reads messages from application.properties
In this example, I use ConstraintValidatorContext to create the custom error message
References:
Why we need a custom constraint validator, of course there are too much reasons for this question. Here I will show you 2 reasons:
- I need a new validator to validate a field matches a specified pattern or not
- I need to make the comparison between two fields (cross field validation)
1. Import BeanUtils
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version> </dependency>
I use this util to get the value of a field in an object. This util is not important, you can use another one
2. Create a new properties used in Custom Constraint Validator
Create new ValidationMessages.properties file in folder src/main/resourcesWhen <form:errors> renders error messages on form, it will look in this file, for more information, please read Message interpolation.
Note: by default, the normal Hibernate Validator still reads messages from application.properties
3. Create a Custom Constraint Validator
There are many ways to create a custom constraint validator, here I will show you two ways.3.1 Custom Constraint Validator to validate a field
According to Hibernate, to create a custom constraint, the following three steps are required:- Create a constraint annotation
- Implement a validator
- Define a default error message
3.1.1 Constraint annotation
I do not detail about how to create an annotation in this article, for more detail, read it in oracle@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=CodeValidatorImpl.class)
public @interface CodeValidator {
String message() default "{code.not.correct}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
- Line 1, it means: this annotation is applied for a field
- Line 2, it means: annotations of this type will be available at runtime by the means of reflection
- Line 3 specifies the validator to be used to validate elements annotated with @CodeValidator
- Line 5 defines the default message of this validator
3.1.2 Constraint validator
public class CodeValidatorImpl implements ConstraintValidator<CodeValidator, String> {
@Override
public void initialize(CodeValidator arg0) {
}
@Override
public boolean isValid(String codeValue, ConstraintValidatorContext ctx) {
return checkNull(codeValue, ctx) && checkMatched(codeValue, ctx);
}
private boolean checkNull(String codeValue, ConstraintValidatorContext ctx) {
boolean isValid;
if (codeValue == null || codeValue.equals("")) {
isValid = false;
} else {
isValid = true;
}
if (!isValid) {
ctx.disableDefaultConstraintViolation();
ctx.buildConstraintViolationWithTemplate("{code.required}").addConstraintViolation();
}
return isValid;
}
private boolean checkMatched(String codeValue, ConstraintValidatorContext ctx) {
Pattern pattern = Pattern.compile("[0-9a-zA-Z]*");
Matcher matcher = pattern.matcher(codeValue);
if (!matcher.matches()) {
ctx.disableDefaultConstraintViolation();
ctx.buildConstraintViolationWithTemplate("{code.contain.only}").addConstraintViolation();
return false;
} else {
return true;
}
}
}
- Line 1, ConstraintValidator<CodeValidator, String> type String means we use this validator to validate a String field.
- Line 15, Make your validation in isValid function
3.1.3 Error message
In this example, I use ConstraintValidatorContext to create the custom error message
ctx.disableDefaultConstraintViolation();
ctx.buildConstraintViolationWithTemplate("{code.required}").addConstraintViolation();
These commands mean: Do not use the default message, let use the message with key = "code.required" in properties file (ValidationMessages.properties)
3.1.4 In Bean
Use the custom constraint as well as the normal constraintpublic class User {
@NotEmpty(message="user.firstName")
private String firstName;
@NotEmpty
private String lastName;
@CodeValidator
private String code;
....
}
3.2 Custom constraint for cross field validation
There are a few difference with the first way.3.2.1 Constraint annotation
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=PasswordMatchImpl.class)
public @interface PasswordMatch {
String message() default "{notmatch.password}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* First field
*/
String password();
/**
* Second field
*/
String repassword();
}
- Difference in Target
- We require 2 fields in declaration of annotation
3.2.2 Constraint validator
public class PasswordMatchImpl implements ConstraintValidator<PasswordMatch, Object> {
private String password;
private String repassword;
@Override
public void initialize(PasswordMatch pm) {
password = pm.password();
repassword = pm.repassword();
}
@Override
public boolean isValid(Object obj, ConstraintValidatorContext ctx) {
try {
// get field value
Object pw = BeanUtils.getProperty(obj, password);
Object rpw = BeanUtils.getProperty(obj, repassword);
return pw != null && rpw != null && pw.equals(rpw);
} catch (Exception ex) {
return false;
}
}
}
- ConstraintValidator<PasswordMatch, Object>, the argument type is Object
- Initialize the attribute in initialize function (it's the name of field, not the value of field)
- We get value of field through the obj parameter
3.2.3 In Bean
@PasswordMatch(password="password", repassword="repassword")
public class User {
@NotEmpty(message="user.firstName")
private String firstName;
...
}
- We validate for an object, not for a field
- The parameters are required
4. Error message in JSP
As normal validation, we use <form:errors> to show the error message<tr> <td><form:label path="firstName">First Name</form:label></td> <td> <form:input path="firstName" /> <form:errors path="firstName" class="errors" /> </td> </tr>
References:
- http://docs.jboss.org/hibernate/validator/4.2/reference/en-US/html/validator-usingvalidator.html#section-message-interpolation
- http://docs.jboss.org/hibernate/validator/4.2/reference/en-US/html/validator-customconstraints.html
- http://stackoverflow.com/questions/1972933/cross-field-validation-with-hibernate-validator-jsr-303
Source code:
- For the demonstration, please download source code from HERE
- My repository on Github, download ALL SOURCE code in this blog