2017-10-05 7 views
4

J'ai créé un petit exemple de projet pour montrer deux problèmes je rencontre dans la configuration de la validation Spring Boot et son intégration avec Hibernate. J'ai déjà essayé d'autres réponses que j'ai trouvées sur le sujet mais malheureusement elles n'ont pas fonctionné pour moi ou ont demandé de désactiver la validation Hibernate.Injecter référentiel intérieur ConstraintValidator avec configuration Spring 4 et interpolation des messages

Je veux utiliser un validateur personnalisé implémentant ConstraintValidator<ValidUser, User> et y injecter mon UserRepository. En même temps je veux garder le comportement par défaut de Hibernate qui vérifie les erreurs de validation pendant la mise à jour/persister.

J'écris ici pour sections complétude de l'application.

configuration personnalisée Dans cette classe, je mets un validateur personnalisé avec une coutume MessageSource, donc Spring lire les messages du fichier resources/messages.properties

@Configuration 
public class CustomConfiguration { 

    @Bean 
    public MessageSource messageSource() { 
     ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); 
     messageSource.setBasenames("classpath:/messages"); 
     messageSource.setUseCodeAsDefaultMessage(false); 
     messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1)); 
     messageSource.setFallbackToSystemLocale(false); 
     return messageSource; 
    } 

    @Bean 
    public LocalValidatorFactoryBean validator() { 
     LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); 
     factoryBean.setValidationMessageSource(messageSource()); 
     return factoryBean; 
    } 

    @Bean 
    public MethodValidationPostProcessor methodValidationPostProcessor() { 
     MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor(); 
     methodValidationPostProcessor.setValidator(validator()); 
     return methodValidationPostProcessor; 
    } 

} 

Le haricot Rien de spécial sinon la coutume validateur @ValidUser

@ValidUser 
@Entity 
public class User extends AbstractPersistable<Long> { 
    private static final long serialVersionUID = 1119004705847418599L; 

    @NotBlank 
    @Column(nullable = false) 
    private String name; 

    /** CONTACT INFORMATION **/ 

    @Pattern(regexp = "^\\+{1}[1-9]\\d{1,14}$") 
    private String landlinePhone; 

    @Pattern(regexp = "^\\+{1}[1-9]\\d{1,14}$") 
    private String mobilePhone; 

    @NotBlank 
    @Column(nullable = false, unique = true) 
    private String username; 

    @Email 
    private String email; 

    @JsonIgnore 
    private String password; 

    @Min(value = 0) 
    private BigDecimal cashFund = BigDecimal.ZERO; 

    public User() { 

    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public String getLandlinePhone() { 
     return landlinePhone; 
    } 

    public void setLandlinePhone(String landlinePhone) { 
     this.landlinePhone = landlinePhone; 
    } 

    public String getMobilePhone() { 
     return mobilePhone; 
    } 

    public void setMobilePhone(String mobilePhone) { 
     this.mobilePhone = mobilePhone; 
    } 

    public String getUsername() { 
     return username; 
    } 

    public void setUsername(String username) { 
     this.username = username; 
    } 

    public String getEmail() { 
     return email; 
    } 

    public void setEmail(String email) { 
     this.email = email; 
    } 

    public String getPassword() { 
     return password; 
    } 

    public void setPassword(String password) { 
     this.password = password; 
    } 

    public BigDecimal getCashFund() { 
     return cashFund; 
    } 

    public void setCashFund(BigDecimal cashFund) { 
     this.cashFund = cashFund; 
    } 

} 

Validateur personnalisé C'est ici que j'essaie d'injecter le référentiel. Le référentiel est toujours nul sinon quand je désactive la validation Hibernate.

public class UserValidator implements ConstraintValidator<ValidUser, User> { 
    private Logger log = LogManager.getLogger(); 

    @Autowired 
    private UserRepository userRepository; 

    @Override 
    public void initialize(ValidUser constraintAnnotation) { 
    } 

    @Override 
    public boolean isValid(User value, ConstraintValidatorContext context) { 
     try { 
      User foundUser = userRepository.findByUsername(value.getUsername()); 

      if (foundUser != null && foundUser.getId() != value.getId()) { 
       context.disableDefaultConstraintViolation(); 
       context.buildConstraintViolationWithTemplate("{ValidUser.unique.username}").addConstraintViolation(); 

       return false; 
      } 
     } catch (Exception e) { 
      log.error("", e); 
      return false; 
     } 
     return true; 
    } 

} 

messages.properties

#CUSTOM VALIDATORS 
ValidUser.message = I dati inseriti non sono validi. Verificare nuovamente e ripetere l'operazione. 
ValidUser.unique.username = L'username [${validatedValue.getUsername()}] è già stato utilizzato. Sceglierne un altro e ripetere l'operazione. 

#DEFAULT VALIDATORS 
org.hibernate.validator.constraints.NotBlank.message = Il campo non può essere vuoto 

# === USER === 
Pattern.user.landlinePhone = Il numero di telefono non è valido. Dovrebbe essere nel formato E.123 internazionale (https://en.wikipedia.org/wiki/E.123) 

Dans mes tests, vous pouvez essayer à partir du code source, j'ai deux problèmes:

  1. Le dépôt est injecté à l'intérieur UserValidator null si je ne désactive pas la validation Mise en veille prolongée (spring.jpa.properties.javax.persistence.validation.mode = none)
  2. Même si je désactive le validateur Hibernate, mes cas de test échouent parce que quelque chose empêche Spring d'utiliser l'interpolation de chaîne par défaut pour les messages de validation qui devraient ressembler à [Constraint]. [Nom de classe minuscule]. [PropertyName]. Je ne veux pas utiliser l'annotation de contrainte avec l'élément de valeur comme ceci @NotBlank(message="{mycustom.message}") parce que je ne vois pas le point étant donné qu'il a sa propre convetion pour l'interpolation et je peux profiter de cela ... cela signifie moins de codage.

I attach the code; vous pouvez simplement exécuter des tests Junit et voir des erreurs (la validation Hibernate est activée, vérifiez application.properties).

Qu'est-ce que je fais mal? Que pouvais-je faire pour résoudre ces deux problèmes?

MISE À JOUR ====== ======

Juste pour clarifier les choses, la lecture des documents de validation de printemps https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring-constraints ils disent:

Par défaut, le LocalValidatorFactoryBean configure un SpringConstraintValidatorFactory qui utilise Spring pour créer des instances ConstraintValidator. Cela permet à vos ConstraintValidators personnalisés de bénéficier de l'injection de dépendances comme n'importe quel autre bean Spring. Comme vous pouvez le voir, une implémentation de ConstraintValidator peut avoir ses dépendances @Autowired comme n'importe quel autre bean Spring.

Dans ma classe de configuration, j'ai créé mon LocalValidatorFactoryBean en cours d'écriture.

Une autre question intéressante est this et this, mais je n'ai pas eu de chance avec eux.

MISE À JOUR ====== ====== 2

Après beaucoup de Reseach, semble avec Hibernate Validator l'injection n'est pas fourni.

J'ai trouvé un couple de façon, vous pouvez le faire:

1er chemin

Créer cette classe de configuration:

@Configuration 
public class HibernateValidationConfiguration extends HibernateJpaAutoConfiguration { 

    public HibernateValidationConfiguration(DataSource dataSource, JpaProperties jpaProperties, 
      ObjectProvider<JtaTransactionManager> jtaTransactionManager, 
      ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) { 
     super(dataSource, jpaProperties, jtaTransactionManager, transactionManagerCustomizers); 
    } 

    @Autowired 
    private Validator validator; 

    @Override 
    protected void customizeVendorProperties(Map<String, Object> vendorProperties) { 
     super.customizeVendorProperties(vendorProperties); 
     vendorProperties.put("javax.persistence.validation.factory", validator); 
    } 
} 

2ème voie

Créer un utilitaire haricot

@Service 
public class BeanUtil implements ApplicationContextAware { 

    private static ApplicationContext context; 

    @Override 

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 

     context = applicationContext; 

    } 

    public static <T> T getBean(Class<T> beanClass) { 

     return context.getBean(beanClass); 

    } 

} 

puis dans l'initialisation du validateur:

@Override 
public void initialize(ValidUser constraintAnnotation) { 
userRepository = BeanUtil.getBean(UserRepository.class); 
em = BeanUtil.getBean(EntityManager.class); 
} 

très important Dans les deux cas

, afin de rendre le fonctionnement Vous devez « réinitialiser » le gestionnaire d'entités de cette façon :

@Override 
public boolean isValid(User value, ConstraintValidatorContext context) { 
    try { 
     em.setFlushMode(FlushModeType.COMMIT); 
     //your code 
    } finally { 
     em.setFlushMode(FlushModeType.AUTO); 
    } 
} 

De toute façon, je ne sais pas si c'est vraiment un moyen sûr. Probably it's not a good practice access to the persistence layer at all.

Répondre

0

Si vous avez vraiment besoin d'utiliser l'injection dans votre Validator essayez d'ajouter @Configurable annotation sur elle:

@Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true) 
public class UserValidator implements ConstraintValidator<ValidUser, User> { 
    private Logger log = LogManager.getLogger(); 

    @Autowired 
    private UserRepository userRepository; 

    // this initialize method wouldn't be needed if you use HV 6.0 as it has a default implementation now 
    @Override 
    public void initialize(ValidUser constraintAnnotation) { 
    } 

    @Override 
    public boolean isValid(User value, ConstraintValidatorContext context) { 
     try { 
      User foundUser = userRepository.findByUsername(value.getUsername()); 

      if (foundUser != null && foundUser.getId() != value.getId()) { 
       context.disableDefaultConstraintViolation(); 
       context.buildConstraintViolationWithTemplate("{ValidUser.unique.username}").addConstraintViolation(); 

       return false; 
      } 
     } catch (Exception e) { 
      log.error("", e); 
      return false; 
     } 
     return true; 
    } 

} 

De la documentation à cette annotation:

marque une classe comme étant admissible à Printemps- configuration entraînée

Cela devrait résoudre votre problème nul. Pour que cela fonctionne, vous devez configurer AspectJ ...(Vérifiez comment utiliser @Configurable au printemps pour cela)

+0

Merci pour la réponse. J'ai mis à jour ma question en soulignant que l'injection de dépendance devrait fonctionner sans aucune astuce spéciale basée sur ce qu'ils disent. Cependant, j'essaie de suivre votre chemin en utilisant EnableSpringConfigured et EnableLoadTimeWeaving mais le référentiel injecté est toujours nul. – drenda