2017-10-05 165 views
4

我创建了一个小的示例项目,以显示我在配置Spring Boot验证及其与Hibernate集成时遇到的两个问题。 我已经尝试了其他关于该主题的回复,但不幸的是,它们不适用于我或者要求禁用Hibernate验证。使用Spring 4和消息插值配置在ConstraintValidator中注入库

我想使用自定义验证器实现ConstraintValidator<ValidUser, User>并注入它我的UserRepository。 同时我想保持Hibernate的默认行为,在更新/保留期间检查验证错误。

我在这里写的完整性主要部分的应用程序。

自定义配置 在这个类中我设置一个自定义的验证与自定义MessageSource,所以Spring将从文件读取消息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; 
    } 

} 

豆子 这里没有什么特别,如果没有自定义验证器@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; 
    } 

} 

自定义验证器 这里是我尝试注入存储库的地方。当我禁用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) 

在我的测试中,可以从源代码试试,我有两个问题:

  1. UserValidator注入的仓库是空如果我不禁用Hibernate验证(spring.jpa.properties.javax.persistence.validation.mode = none)
  2. 即使我禁用Hibernate验证器,我的测试用例也会失败,因为某些情况会阻止Spring使用默认字符串插值来验证消息,该消息应该是[Constraint]。[class name lowercase]。[propertyName]。我不想使用带有这个值元素的约束注释,就像这个@NotBlank(message="{mycustom.message}")一样,因为我没有看到考虑到它有自己的内插对应点,我可以利用那个......这意味着更少的编码。

​​;您可以运行Junit测试并查看错误(Hibernate验证已启用,请检查application.properties)。

我在做什么错?我能做些什么来解决这两个问题?

====== UPDATE ======

只是为了澄清,读春天验证文件https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring-constraints他们说:

默认情况下,LocalValidatorFactoryBean配置使用Spring时,SpringConstraintValidatorFactory创建ConstraintValidator实例。这允许您的自定义ConstraintValidators像任何其他Spring bean一样受益于依赖注入。如你所见,一个ConstraintValidator实现可能像其他Spring bean一样拥有@Autowired的依赖关系。

在我的配置类中,我在他们编写时创建了我的LocalValidatorFactoryBean

另一个有趣的问题是thisthis,但我没有运气与他们。

======更新2 ======

很多研制后,似乎有不提供注入Hibernate的验证。

我发现一对夫妇的方式,你可以这样做:

1路

创建此配置类:

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

第二方式

创建工具豆

@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); 

    } 

} 

,然后在验证初始化:

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

非常重要

在这两种情况下,为了使这种方式它的工作原理,你必须“复位”的实体管理器:

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

无论如何,我不知道这是否真的是一种安全的方式。 Probably it's not a good practice access to the persistence layer at all

回答

0

如果你真的需要在验证使用注射尝试将它添加@Configurable注释:

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

} 

从文档到注释:

标记一个类为符合春季 - 驱动配置

所以这应该解决您的空问题。为了使它工作,你需要配置AspectJ ...(请查看如何在Spring中使用@Configurable)

+0

感谢您的回复。我更新了我的问题,指出依赖注入应该没有根据他们说什么的特殊技巧。但是我试图按照自己的方式使用EnableSpringConfigured和EnableLoadTimeWeaving,但注入的存储库始终为空。 – drenda

相关问题