I'm using Spring Boot 2.2.4 and Hibernate 5.4.10. I cannot understand why the validator objects are created twice: the first time with Spring Boot context and the second time without it. I tried javax.persistance.validation.mode=none - nothing changed. Main problem is that objects created with Spring context are ignored.
Annotation:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {CustomForBaseEntity.class, CustomForDto.class})
public #interface Custom {
String message() default "{Your license plate has the wrong format. "
+ "Please don't use special symbols.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Base class:
abstract class CustomValidator<T> implements ConstraintValidator<LicensePlateValidation, T> {
private Pattern dependency;
#Autowired
public void setSpringDependency(#Value("${path}") final String dependency) {
this.dependency = Pattern.compile(dependency);
}
}
Both derived classes looks same, so there is only one:
#Component
public class CustomForBaseEntity extends LicensePlateValidator<Vehicle> {
#Override
public boolean isValid(Vehicle vehicle, ConstraintValidatorContext constraintValidatorContext) {
String licensePlate = vehicle.getLicensePlate();
return checkLicensePlate(licensePlate, constraintValidatorContext);
}
}
As mentioned in Spring Boot documentation, I'm created bean of LocalValidatorFactoryBean:
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
I am looking for annotation to annotate pojo class which I need to validate during request deserialization. I am searching for annotation to pass as parameter class which will validate my pojo.
Implementation can look like that:
#ValidateAnnotation(class = ExampleClassValidator.class)
public class ExampleClass {
private String name;
}
Has anyone know any of spring annotation for that approach or some dependency which offer that declarative validation ? I am asking because I cannot find any similar solution in documentation.
You can use #InitBinder to configure a validator based on the target of the method. Here's a simple example:
Annotation class:
package test.xyz;
import org.springframework.validation.Validator;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidateAnnotation {
Class<? extends Validator> value();
}
The example class to be validated:
package test.xyz;
#ValidateAnnotation(ExampleClassValidator.class)
public class ExampleClass {
}
The validator class:
package test.xyz;
import org.springframework.validation.Errors;
public class ExampleClassValidator implements org.springframework.validation.Validator {
#Override
public boolean supports(Class<?> aClass) {
return ExampleClass.class.isAssignableFrom(aClass);
}
#Override
public void validate(Object o, Errors errors) {
}
}
And finally the controller class with the #InitBinder definition:
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import test.xyz.ExampleClass;
import test.xyz.ValidateAnnotation;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.Collections;
#Controller
public class ExampleController {
#RequestMapping(value="test-endpoint", method= RequestMethod.GET)
public #ResponseBody
Object testMethod(#Valid ExampleClass exampleClass, Errors errors) {
return Collections.singletonMap("success", true);
}
#InitBinder
public void initBinder(WebDataBinder binder, HttpServletRequest request) throws IllegalAccessException, InstantiationException {
Class<?> targetClass = binder.getTarget().getClass();
if(targetClass.isAnnotationPresent(ValidateAnnotation.class)) {
ValidateAnnotation annotation = targetClass.getAnnotation(ValidateAnnotation.class);
Class<? extends Validator> value = annotation.value();
Validator validator = value.newInstance();
binder.setValidator(validator);
}
}
}
Explanation:
You can use the WebDataBinder's getTarget method to access the target to be validated. From there it is straightforward to check the annotation on the class, get the validator class, and set it on the binder. I believe you can also use the #ControllerAdvice annotation to configure a global InitBinder. As a disclaimer, I don't know if it is recommended to access the binder target within the InitBinder, but I haven't had any issues the few times I've done so.
For normal validation you can annotate your class with the annotations from the javax.validation.constraints package, like javax.validation.constraints.NotEmpty. For custom validation, you can make your own annotation that will call a custom validator that you write.
For example, if you wanted to create a validator that makes sure a field is nine characters long you could do the following:
First, create your custom validation annotation.
#Documented
#Constraint(validatedBy = NineCharactersValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface NineCharactersOnly {
String message() default "This field must contain exactly nine characters";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Next, create your custom validator:
public class NineCharactersValidator implements ConstraintValidator<NineCharactersOnly, String> {
#Override
public void initialize(NineCharactersOnly contactNumber) {
}
#Override
public boolean isValid(String contactField, ConstraintValidatorContext cxt) {
return contactField != null && contactField.length() == 9;
}
}
Next, use the annotation on fields that need to be constrained on your pojo.
public class ExampleClass {
#NineCharactersOnly
private String fieldThatMustBeNineCharacters;
}
Next, mark your method parameters in the controller with #Valid so they will be validated by Spring:
#RestController
public class CustomValidationController {
#PostMapping("/customValidationPost")
public ResponseEntity<String> customValidation(#Valid ExampleClass exampleClass, BindingResult result, Model m) {
// we know the data is valid if we get this far because Spring automatically validates the input and
// throws a MethodArgumentNotValidException if it's invalid and returns an HTTP response of 400 (Bad Request).
return ResponseEntity.ok("Data is valid");
}
}
Finally, if you want custom logic for handling validation errors instead of just sending a 400, you can create a custom validation handler method.
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationException(MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
d.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
Maybe writing your custom annotation and using Spring AOP will help you. Spring AOP is quite simple.
I found pretty good solution but in one place i used reflection :(
Please feel free to comment and rate this solution, is it good enough or something could be done better.
I had create own annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Validator {
Class<? extends org.springframework.validation.Validator> validator();
}
I next step I extend LocalValidatorFactoryBean to override validate method and here I was forced to use reflection to get class from annotation.
#Component
#RequiredArgsConstructor
class CustomLocalValidatorFactoryBean extends LocalValidatorFactoryBean {
private final Map<Class<? extends Validator>, Validator> validators;
#Override
public void validate(Object target, Errors errors, Object... validationHints) {
Class<? extends Validator> validatorKey = target.getClass().getAnnotation(com.validation.validator.Validator.class).validator();
Optional.ofNullable(validators.get(validatorKey)).ifPresentOrElse(
validator ->
validator.validate(target, errors),
() -> super.validate(target, errors, validationHints)
);
}
}
I annotate pojo with my annotation to specify validator.
#Data
#Validator(validator = PersonValidator.class)
public class PersonDto {
private final String name;
private final String surname;
private final Integer age;
}
As you can see in my CustomLocalValidatorFactoryBean I injected a map of validators, in this map i store validators assigned to key which is the class of this validator. This class i specify in annotation in pojo to fetch suitable validator for currect validate target. And this is my configuration of validatos map.
#Configuration
class ValidatorConfig {
#Bean
Map<Class<? extends Validator>, Validator> validators() {
var validators = new HashMap<Class<? extends Validator>, Validator>();
validators.put(PersonValidator.class, new PersonValidator());
return validators;
}
}
I specify custom #RestControllerAdvice and override method handleMethodArgumentNotValid.
#RestControllerAdvice
class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = error.getCode();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
And this is my validator, it could be bean with dao acces but it could also be simple pojo.
public class PersonValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return PersonDto.class.isAssignableFrom(aClass);
}
#Override
public void validate(Object object, Errors errors) {
Optional.of(object).map(obj -> (PersonDto) obj).ifPresent(person -> {
Optional.ofNullable(person.getName())
.filter(name -> Strings.isNotBlank(name) && name.length() >= 3)
.ifPresentOrElse(name -> doNothing(), () -> errors.reject("person.name", "name of person is invalid!"));
});
}
}
What do you think about that configuration, is it cannon on sparrow or you just like that solution ?
config class
#ComponentScan(basePackages = {"validator"})
class AppConfiguration { ... }
annotation class
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueLoginValidator.class)
public #interface UniqueLogin {
String message() default "{com.dolszewski.blog.UniqueLogin.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
validator class
#Component
class UniqueLoginValidator implements ConstraintValidator<UniqueLogin, String> {
private UserRepository userRepository;
public UniqueLoginValidator(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void initialize(UniqueLogin constraint) {
}
public boolean isValid(String login, ConstraintValidatorContext context) {
return login != null && !userRepository.findByLogin(login).isPresent();
}
}
I have a class with property #UniqueLogin String login, I also use other annotations like #Size and #Max, the last 2 works, but my custom annotation does not work.
Can you please help to understand why spring do not call custom validator?
It worked for me to create inside src/main/resources/META-INF/services a file named javax.validation.ConstraintValidator with a list new line separated of all qualified name of custom constraint validators you created.
This way, Spring will automatically register the custom validator.
This file will be automatically checked from Spring and included into built artifact.
Be careful of annotation configuration after applying this solution. You should annotate with #Constraint(validatedBy = { }) to prevent double validator initialization.
I'd like to have an annotation that validates that a MultipartFile is an image. I've created an #interface and a ConstraintValidator, and added the annotation to my field.
Other validation annotations, like #NotEmpty and #Size(min = 0, max = 2) are working fine.
Here is the code in summary. This question has the same problem, but the answer doesn't work for me.
Form.java:
#Validated
public class Form {
#MultipartImage
private MultipartFile image;
...
}
#Interface MultipartImage
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import validation.MultipartFileImageConstraintValidator;
#Documented
#Constraint(validatedBy = { MultipartFileImageConstraintValidator.class })
#Target({ LOCAL_VARIABLE, FIELD, METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface MultipartImage {
String message() default "{MultipartImage.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The validator, MultipartFileConstraintValidator.java
import java.io.IOException;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.web.multipart.MultipartFile;
public class MultipartFileConstraintValidator implements ConstraintValidator<MultipartImage, MultipartFile> {
#Override
public void initialize(final MultipartImage constraintAnnotation) {
}
#Override
public boolean isValid(final MultipartFile file, final ConstraintValidatorContext context) {
return false;
}
Here's the form submit method in the controller
#RequestMapping(value = "/formsubmit", method = RequestMethod.POST)
public ModelAndView handleForm(#Validated final Form form,
final BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
...
// returns the model
}
}
Validator set up in the #Configuration file, see https://stackoverflow.com/a/21965098/4161471
#Configuration
#ConfigurationProperties("static")
#AutoConfigureAfter(DispatcherServletAutoConfiguration.class)
public class StaticResourceConfig extends WebMvcConfigurerAdapter {
...
#Bean(name = "validator")
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
final MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
#Override
public Validator getValidator() {
return validator();
}
#Bean
public ReloadableResourceBundleMessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
// Load files containing message keys.
// Order matters. The first files override later files.
messageSource.setBasenames(//
// load messages and ValidationMessages from a folder relative to the jar
"file:locale/messages", //
"file:locale/ValidationMessages", //
// load from within the jar
"classpath:locale/messages", //
"classpath:locale/ValidationMessages" //
);
messageSource.getBasenameSet();
messageSource.setCacheSeconds(10); // reload messages every 10 seconds
return messageSource;
}
}
There was information missing from my original code, specifically regarding the controller, where an additional validator is defined and bound. It uses the wrong method to include the validator FormValidator, and overrides the annotation validations.
binder.setValidator(formValidator) overrides any other validator. Instead binder.addValidators(formValidator) should be used!
#Controller
public class FormController {
#Autowired
final private FormValidator formValidator;
#InitBinder("form")
protected void initBinder(WebDataBinder binder) {
// correct
binder.addValidators(formValidator);
// wrong
//binder.setValidator(formValidator);
}
...
#RequestMapping(value = "/formsubmit", method = RequestMethod.POST)
public ModelAndView handleForm(#Validated final Form form, final BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
...
// returns the model
}
...
}
}
I have also removed the Bean MethodValidationPostProcessor in the #Configuration class.
I am trying to perform validation in spring and for that I need some data to be available before I execute the validation. That data is in my sql inside the table . I am looking for solution which will load my spring bean from the mysql table and that bean I can use to get the data for the validation .
Check out this example.. you just need declare your validator as bean.
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyValidatorImpl.class)
#Documented
public #interface MyValidator {
String message() default "invalid";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
#Component // <---- this will allow you to access spring component
public class MyValidatorImpl implements ConstraintValidator<MyValidator, String> {
#Autowired MyDAO myDAO;
public void initialize(MyValidator constraint) {
}
public boolean isValid(String s, ConstraintValidatorContext context) {
return false;
}
}