使用 Spring 4 和消息插值配置在 ConstraintValidator 中注入存储库

Posted

技术标签:

【中文标题】使用 Spring 4 和消息插值配置在 ConstraintValidator 中注入存储库【英文标题】:Inject Repository inside ConstraintValidator with Spring 4 and message interpolation configuration 【发布时间】:2018-03-17 14:36:48 【问题描述】:

我创建了一个小示例项目来展示我在配置 Spring Boot 验证及其与 Hibernate 的集成中遇到的两个问题。 我已经尝试过我找到的有关该主题的其他回复,但不幸的是它们对我不起作用或要求禁用 Hibernate 验证。

我想使用实现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]\\d1,14$")
    private String landlinePhone;

    @Pattern(regexp = "^\\+1[1-9]\\d1,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 验证时,存储库始终为 null。

    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)

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

    如果我不禁用 Hibernate 验证,UserValidator 中的注入存储库为空 (spring.jpa.properties.javax.persistence.validation.mode=none) 即使我禁用 Hibernate 验证器,我的测试用例也会失败,因为某些东西阻止 Spring 对应该类似于 [Constraint].[class name lowercase].[propertyName] 的验证消息使用默认字符串插值。我不想将约束注释与 @NotBlank(message="mycustom.message") 这样的值元素一起使用,因为考虑到它有他自己的插值对流,我看不到这一点,我可以利用这一点......这意味着更少的编码。

I attach the code;您可以只运行 Junit 测试并查看错误(启用 Hibernate 验证,检查 application.properties)。

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

====== 更新 ======

为了澄清,阅读 Spring 验证文档 https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring-constraints 他们说:

默认情况下,LocalValidatorFactoryBean 配置一个 SpringConstraintValidatorFactory,它使用 Spring 创建 ConstraintValidator 实例。这允许您的自定义 ConstraintValidators 像任何其他 Spring bean 一样从依赖注入中受益。

如您所见,ConstraintValidator 实现可能有其依赖项@Autowired,就像任何其他 Spring bean 一样。

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

另外一个有趣的问题是this 和this,但我没有遇到他们。

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

经过大量研究,似乎 Hibernate 验证器没有提供注入。

我找到了几种方法可以做到这一点:

第一种方式

创建这个配置类:

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

第二种方式

创建实用程序 bean

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

【问题讨论】:

:(我遇到了这种情况,我需要使用ConstraintValidatorConstraintValidator 中执行SQL 操作。@drenda 你设法解决了这个问题吗? 【参考方案1】:

如果您确实需要在验证器中使用注入,请尝试在其上添加 @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;
    


从文档到注释:

将类标记为符合 Spring 驱动配置的条件

所以这应该可以解决您的 null 问题。不过,要使其工作,您需要配置 AspectJ...(检查如何在 Spring 中使用 @Configurable)

【讨论】:

感谢您的回复。我更新了我的问题,指出依赖注入应该可以根据他们所说的而无需任何特殊技巧即可工作。但是,我正在尝试使用 EnableSpringConfigured 和 EnableLoadTimeWeaving 遵循您的方式,但注入的存储库始终为空。

以上是关于使用 Spring 4 和消息插值配置在 ConstraintValidator 中注入存储库的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 集成 个推 和 UniPush 两种消息推送方式

PyLint 消息:记录格式插值

react-spring 插值函数不适用于打字稿

Spring-websocket 配置 websocket发送消息(群发)

spring-boot中logback日志配置

springMVc中事务控制无效,循环插值时候一样无法同步数据如何解决?