Зависимость, не автоподдержанная в JSR-303 Custom Validator

У меня есть приложение Spring Boot, которое содержит класс User - все поля имеют стандартные аннотации JSR-303 (@NotNull, @Size и т.д.), И проверка достоверности работает.

Однако, когда я добавляю пользовательскую проверку для пользователя, я не могу получить зависимость, введенную в пользовательский валидатор:

@Component
public class UniqueUsernameValidator implements 
ConstraintValidator<UniqueUsername, String> {

@Autowired
private UserRepository userRepository;

@Override
public boolean isValid(String username, ConstraintValidatorContext context) {
    // implements logic
}

Аннотации @UniqueUsername объявляются как:

@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 { };
}

Аннотированное поле:

@NotBlank
@Size(min = 2, max = 30)
@UniqueUsername
private String username;

И использование валидатора:

@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...
   }
}

Проблема в том, что UserRepository не автоустанавливается в UniqueUsernameValidator. Поле всегда равно нулю.

Я использую LocalValidatorFactoryBean.

Кто-нибудь знает, почему автоустановка не работает?


@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
}

Ответы

Ответ 1

Вам нужно добавить аннотацию @Valid перед классом сущности в общедоступном String createUser (@ModelAttribute ("newUser") User newUser) перед пользователем.

@RequestBody @Valid User user

Ответ 2

Реализация UserRepository требует аннотации типа "@Repository" или "@Component" или "@Service". Пользователь UserService получает экземпляр репозитория через конструктор. Возможно, был использован "новый UserRepositoryDao()". И в вашем валидаторе, который вы пытаетесь сделать autwire. Я предполагаю, что это либо не аннотируется как сервис, либо не загружается в контексте весеннего контекста или как весенний боб в вашем spring.xml

Ответ 3

Несколько месяцев назад я столкнулся с той же проблемой. Вместо автоустройства репозитория передайте службу, которая уже использует тот же репозиторий через аннотацию.

Объявите аннотацию, чтобы принять поле, которое должно быть уникальным, и службу, выполняющую проверку.

@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
}

Используйте его на POJO:

@NotBlank
@Size(min = 2, max = 30)
@UniqueUsername(service = UserService.class, fieldName = "username")
private String username;

Обратите внимание, что служба, переданная в аннотацию (Class<? extends UniqueUsernameValidation> service()) должна реализовать интерфейс UniqueUsernameValidation.

public interface UniqueUsernameValidation {
    public boolean isUnique(Object value, String fieldName) throws Exception;
}

Теперь сделайте переданный UserService реализованным интерфейсом выше и переопределите его только методом:

@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
}

Не забудьте UniqueUsernameValidator который обрабатывает аннотацию:

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;
    }
}