web-dev-qa-db-fra.com

Dépendance non automatiquement câblée dans le validateur personnalisé JSR-303

J'ai une application Spring Boot contenant une classe d'utilisateurs. Tous les champs ont des annotations standard JSR-303 (@NotNull, @Size, etc.) et la validation fonctionne correctement.

Cependant, lorsque j'ajoute une validation personnalisée à Utilisateur, je ne peux pas obtenir une dépendance injectée dans un validateur personnalisé:

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

@Autowired
private UserRepository userRepository;

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

L'annotation @UniqueUsername est déclarée comme:

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

Le champ annoté:

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

Et l'utilisation du validateur:

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

Le problème est que UserRepository n'est pas auto-câblé dans UniqueUsernameValidator. Le champ est toujours nul. 

J'utilise un LocalValidatorFactoryBean. 

Quelqu'un a-t-il une idée du pourquoi l'auto-câblage ne fonctionne pas?


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

Vous devez ajouter une annotation @Valid devant la classe d'entité dans la chaîne publique createUser (@ModelAttribute ("newUser") User newUser) devant User.

@RequestBody @Valid User user
2
Prasad Reddy

J'ai traité le même problème il y a quelques mois. Au lieu de référencer automatiquement le référentiel, transmettez le service qui utilise déjà le même référentiel via l'annotation.

Déclarez que l'annotation accepte le champ requis comme étant unique et un service effectuant la 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
}

Utilisez-le sur le POJO comme:

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

Notez que le service passé dans l'annotation (Class<? extends UniqueUsernameValidation> service()) doit implémenter l'interface UniqueUsernameValidation.

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

Maintenant, assurez-vous que la UserService passée implémente l'interface ci-dessus et remplace sa seule méthode:

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

N'oubliez pas de UniqueUsernameValidator qui traite l'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;
    }
}
0
Nikolas

L'implémentation de UserRepository nécessite une annotation telle que "@Repository" ou "@Component" ou "@Service". Votre UserService obtient l'instance de référentiel via le constructeur. Peut-être qu'un appel "new UserRepositoryDao ()" a été utilisé. Et dans votre validateur, vous essayez de vous connecter automatiquement. Je suppose que ce n'est pas annoté en tant que service OR non chargé dans le chemin du contexte de printemps ou en tant que haricot de printemps dans votre source.xml

0
Arquillian