Reputation:
I have a Spring Boot app that contains an User class - all fields have standard JSR-303 annotations (@NotNull, @Size, etc.) and validation works fine.
However when I add a custom validation to User, I can't get a dependency injected into a custom validator:
@Component
public class UniqueUsernameValidator implements
ConstraintValidator<UniqueUsername, String> {
@Autowired
private UserRepository userRepository;
@Override
public boolean isValid(String username, ConstraintValidatorContext context) {
// implements logic
}
@UniqueUsername annotation is declared as:
@Documented
@Retention(RUNTIME)
@Target({FIELD, ANNOTATION_TYPE, PARAMETER})
@Constraint(validatedBy = UniqueUsernameValidator.class)
@interface UniqueUsername {
String message() default "{com.domain.user.nonUniqueUsername}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
The annotated field:
@NotBlank
@Size(min = 2, max = 30)
@UniqueUsername
private String username;
And the validator usage:
@Service
public final class UserService {
private final UserRepository userRepository;
private final Validator validator;
public UserService(UserRepository userRepository, Validator validator)
{
this.userRepository = userRepository;
this.validator = validator;
}
public void createUser(User user) {
Set<ConstraintViolation<User>> validate = validator.validate(user);
// logic...
}
}
The problem is that UserRepository is not being autowired in UniqueUsernameValidator. Field is always null.
I am using a LocalValidatorFactoryBean.
Does anyone have any idea why autowiring's not working?
@Controller
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/user/new")
public String createUser(@ModelAttribute("newUser") User newUser, BindingResult bindingResult,
Model model) {
userService.createUser(newUser);
// omitted
}
Upvotes: 14
Views: 2237
Reputation: 1157
You need to add @Valid annotation in front of entity class in the public String createUser(@ModelAttribute("newUser") User newUser) in front of User.
@RequestBody @Valid User user
Upvotes: 2
Reputation: 44368
I have dealt with the same problem a few months ago. Instead of autowiring repository, pass the service which already uses the very same repository through the annotation.
Declare the annotation to accept the field required to be unique and a service performing the validation.
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueUsernameValidator.class)
@Documented
public @interface UniqueUsername {
String message() default "{com.domain.user.nonUniqueUsername}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends UniqueUsernameValidation> service(); // Validating service
String fieldName(); // Unique field
}
Use it on the POJO like:
@NotBlank
@Size(min = 2, max = 30)
@UniqueUsername(service = UserService.class, fieldName = "username")
private String username;
Notice the service passed into annotation (Class<? extends UniqueUsernameValidation> service()
) must implement UniqueUsernameValidation
interface.
public interface UniqueUsernameValidation {
public boolean isUnique(Object value, String fieldName) throws Exception;
}
Now make the passed UserService
implement the interface above and override it's only method:
@Override
public boolean isUnique(Object value, String fieldName) throws Exception {
if (!fieldName.equals("username")) {
throw new Exception("Field name not supported");
}
String username = value.toString();
// Here is the logic: Use 'username' to find another username in Repository
}
Don't forget to UniqueUsernameValidator
which processes the annotation:
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, Object>
{
@Autowired
private ApplicationContext applicationContext;
private UniqueUsernameValidation service;
private String fieldName;
@Override
public void initialize(UniqueUsername unique) {
Class<? extends UniqueUsernameValidation> clazz = unique.service();
this.fieldName = unique.fieldName();
try {
this.service = this.applicationContext.getBean(clazz);
} catch(Exception ex) {
// Cant't initialize service which extends UniqueUsernameValidator
}
}
@Override
public boolean isValid(Object o, ConstraintValidatorContext context) {
if (this.service !=null) {
// here you check whether the username is unique using UserRepository
return this.service.isUnique(o, this.fieldName))
}
return false;
}
}
Upvotes: 0
Reputation: 143
The UserRepository implementation needs an Annotation like "@Repository" or "@Component" or "@Service". Your UserService gets the repository instance via constructor. Maybe there was a "new UserRepositoryDao()" call used. And in your validator you are trying to autowire. I guess it's either not annotated as service OR not loaded in the spring context path or as a spring bean in your spring.xml
Upvotes: 0