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
Do you have any idea if cross field validation could be applied at field level?
ReplyDeleteSo for example above tutorial will display the "password not match" at top of the page, what if i need the message to get displayed at field level?