spring - 使用 Spring 4 和消息插值配置在 ConstraintValidator 中注入(inject)存储库

标签 spring hibernate hibernate-validator spring-validator

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

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

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

自定义配置
在这个类中,我使用自定义 MessageSource 设置了一个自定义 validator 。 ,因此 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;
    }

}

bean
如果不是自定义 validator ,这里没什么特别的 @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;
    }

}

自定义 validator
这是我尝试注入(inject)存储库的地方。如果我禁用 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;
    }

}

消息属性
#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)

在我的测试中,您可以从源代码中尝试,我有两个问题:
  • UserValidator 内注入(inject)的存储库如果我不禁用 Hibernate 验证,则为 null (spring.jpa.properties.javax.persistence.validation.mode=none)
  • 即使我禁用 Hibernate validator ,我的测试用例也会失败,因为某些东西阻止 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他们说:

    By default, the LocalValidatorFactoryBean configures a SpringConstraintValidatorFactory that uses Spring to create ConstraintValidator instances. This allows your custom ConstraintValidators to benefit from dependency injection like any other Spring bean.

    As you can see, a ConstraintValidator implementation may have its dependencies @Autowired like any other Spring bean.



    在我的配置类中,我创建了我的 LocalValidatorFactoryBean正如他们所写。

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

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

    经过大量研究,似乎 Hibernate validator 没有提供注入(inject)。

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

    第一种方式

    创建这个配置类:
     @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);
    
        }
    
    }
    

    然后在 validator 初始化中:
    @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 .

    最佳答案

    如果您确实需要在 validator 中使用注入(inject),请尝试添加 @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;
        }
    
    }
    

    从文档到该注释:

    Marks a class as being eligible for Spring-driven configuration



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

    关于spring - 使用 Spring 4 和消息插值配置在 ConstraintValidator 中注入(inject)存储库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46594706/

    相关文章:

    java - Spring MVC 方法中字符串的 Hibernate validator

    grails - 如何从自定义验证器Grails中的另一个Domain类引用属性?

    来自简单 SQL 查询的 Hibernate 条件查询(带有嵌套子查询)

    spring-boot - Gradle 选择了错误的依赖版本

    java - Spring MVC + Hibernate 具有双向关系

    java - 无法在 hibernate 中保存ManyToMany自引用关系

    java - 使用 Spring 的 XML 配置解析器

    java - Tomcat 给出 org.springframework.beans.factory.CannotLoadBeanClassException :

    postgresql - 为什么 PostgreSql 的 Hibernate 默认生成器是 "SequenceGenerator",而不是 "IdentityGenerator"?

    java - JPA : @PrimaryKeyJoinColumn or @JoinColumn + @Id