The custom constraint annotations (JSR 303/349/380) can be created as specified by the JavaBean validation specification (check out example here).
In spring if we register LocalValidatorFactoryBean to bootstrap javax.validation.ValidatorFactory then custom ConstraintValidator classes are loaded as Spring Bean. That means we can have benefit of Spring's dependency injection in validator classes.
Example
Creating custom constraint annotation
package com.logicbig.example;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = LanguageValidator.class)
public @interface Language {
String message() default "must be a valid language display name," +
" found: ${validatedValue}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
ConstraintValidator implementation
package com.logicbig.example;
import org.springframework.beans.factory.annotation.Autowired;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class LanguageValidator implements ConstraintValidator<Language, String> {
@Autowired
private LanguageProvider languageProvider;
@Override
public void initialize(Language constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
return languageProvider.getLanguages()
.stream()
.anyMatch(value::equalsIgnoreCase);
}
}
As seen above we are injecting other spring bean in the validator class.
LanguageProvider bean
public interface LanguageProvider {
List<String> getLanguages();
@Component
public static class DefaultLanguageProvider implements LanguageProvider {
@Override
public List<String> getLanguages() {
List<String> languageList = new ArrayList<>();
for (Locale locale : Locale.getAvailableLocales()) {
languageList.add(locale.getDisplayLanguage());
}
return languageList;
}
}
}
Example POJO and Client Bean
public class Book {
@NotNull
private String name;
@Language
private String language;
.............
}
@Component
public class ClientBean {
@Autowired
private Validator validator;
public void run() {
Book book = new Book();
book.setName("Alien Explorer");
book.setLanguage("Englis");
Set<ConstraintViolation<Book>> c = validator.validate(book);
if (c.size() > 0) {
System.err.println("validation errors:");
c.stream().map(v -> v.getMessage()).forEach(System.err::println);
}
}
}
JavaConfig and main class
@Configuration
@ComponentScan
public class AppConfig {
@Bean
public Validator validatorFactory() {
return new LocalValidatorFactoryBean();
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(
AppConfig.class);
ClientBean bean = context.getBean(ClientBean.class);
bean.run();
}
}
Outputvalidation errors: must be a valid language display name, found: Englis
Example ProjectDependencies and Technologies Used: - spring-context 6.1.2 (Spring Context)
Version Compatibility: 3.2.10.RELEASE - 6.1.2 Version compatibilities of spring-context with this example: Versions in green have been tested.
- hibernate-validator 8.0.1.Final (Hibernate's Jakarta Bean Validation reference implementation)
- expressly 5.0.0 (Jakarta Expression Language Implementation)
- JDK 17
- Maven 3.8.1
|