是否可以将接口用作validatedBy?

huangapple go评论66阅读模式
英文:

Is it possible to use an interface as a validatedBy

问题

我想将验证器的声明和实现分割得与这个问题中的方式非常相似,适用于我的Spring Boot环境。看起来我已经以某种方式使它几乎工作了。我看到我的验证器实际上是由Spring验证调用的,但在执行验证后,Hibernate会抛出异常:

java.lang.NoSuchMethodException: test.UniqueUsernameValidator.<init>()
    at java.base/java.lang.Class.getConstructor0(Class.java:3427) ~[na:na]
    ...

这是因为UniqueUsernameValidator是一个接口(这是预期的)。

我已经按照以下方式配置了Spring(来自于这个答案中的解决方案):

@Bean
public Validator validator() {
  SpringConstraintValidatorFactoryEx scvf =
      new SpringConstraintValidatorFactoryEx(wac.getAutowireCapableBeanFactory());

  LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
  validator.setConstraintValidatorFactory(scvf);
  validator.setApplicationContext(wac);
  validator.afterPropertiesSet();
  return validator;
}

我的自定义验证器:

@Component
@Slf4j
public class SpringConstraintValidatorFactoryEx implements ConstraintValidatorFactory {

  private AutowireCapableBeanFactory beanFactory;

  public SpringConstraintValidatorFactoryEx(AutowireCapableBeanFactory beanFactory) {
    super();
    this.beanFactory = beanFactory;
  }

  public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
    ...
  }

  @Override
  public void releaseInstance(ConstraintValidator<?, ?> instance) {}
}

因此,它尝试按照验证器名称查找Spring Bean。所以我有一个验证器的实现:

@Component
public class UniqueUsernameValidatorImpl implements UniqueUsernameValidator {
  @Autowired private UserCredentialsRepository repository;

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    return repository.findByUsername(value).isEmpty();
  }
}

一个验证器接口:

public interface UniqueUsernameValidator extends ConstraintValidator<UniqueUsername, String> {

}

一个约束注解:

@Target({
  ElementType.METHOD,
  ElementType.FIELD,
  ...
})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(UniqueUsername.List.class)
@Documented
@Constraint(validatedBy = {UniqueUsernameValidator.class})
public @interface UniqueUsername {
  ...
}

看起来Spring验证以某种方式成功地使用了我的验证器实现,但是之后Hibernate在实例化验证器时失败了(?)。我不完全理解为什么会有两种不同的验证机制初始化。

是否有一种方法可以“注册”或“通知”Hibernate验证使用我自定义的验证注解实现?

英文:

I want to split a declaration and implementation of my validator very similar to this question in my Spring boot environment. It looks like I somehow made it almost working. I see that my validator is actually called by Spring validation, but after a validation is executed, Hibernate throws an exception:


java.lang.NoSuchMethodException: test.UniqueUsernameValidator.&lt;init&gt;()
	at java.base/java.lang.Class.getConstructor0(Class.java:3427) ~[na:na]
	at java.base/java.lang.Class.getConstructor(Class.java:2165) ~[na:na]
	at org.hibernate.validator.internal.util.privilegedactions.NewInstance.run(NewInstance.java:41) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl.run(ConstraintValidatorFactoryImpl.java:43) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl.getInstance(ConstraintValidatorFactoryImpl.java:28) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ClassBasedValidatorDescriptor.newInstance(ClassBasedValidatorDescriptor.java:84) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.AbstractConstraintValidatorManagerImpl.createAndInitializeValidator(AbstractConstraintValidatorManagerImpl.java:89) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManagerImpl.getInitializedValidator(ConstraintValidatorManagerImpl.java:117) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getInitializedConstraintValidator(ConstraintTree.java:136) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:54) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:75) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.metadata.core.MetaConstraint.doValidateConstraint(MetaConstraint.java:130) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:123) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:555) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:518) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:488) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:450) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:400) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateCascadedAnnotatedObjectForCurrentGroup(ValidatorImpl.java:629) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateCascadedConstraints(ValidatorImpl.java:590) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateParametersInContext(ValidatorImpl.java:880) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:283) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:235) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
	at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:104) ~[spring-context-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
	at test.AuthenticationController$$EnhancerBySpringCGLIB$$f2b10b56.signUp(&lt;generated&gt;) ~[main/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    ...
	

It's because UniqueUsernameValidator is an interface (as intended).

I've configured Spring like this (solution from this answert to some differenet question):

  @Bean
  public Validator validator() {
    SpringConstraintValidatorFactoryEx scvf =
        new SpringConstraintValidatorFactoryEx(wac.getAutowireCapableBeanFactory());

    LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
    validator.setConstraintValidatorFactory(scvf);
    validator.setApplicationContext(wac);
    validator.afterPropertiesSet();
    return validator;
  }

My custom validator:

@Component
@Slf4j
public class SpringConstraintValidatorFactoryEx implements ConstraintValidatorFactory {

  private AutowireCapableBeanFactory beanFactory;

  public SpringConstraintValidatorFactoryEx(AutowireCapableBeanFactory beanFactory) {
    super();
    this.beanFactory = beanFactory;
  }

  public &lt;T extends ConstraintValidator&lt;?, ?&gt;&gt; T getInstance(Class&lt;T&gt; key) {
    T bean = null;

    try {
      log.info(&quot;Trying to find a validator bean of class &quot; + key.getSimpleName());
      bean = (T) this.beanFactory.getBean(key);
    } catch (BeansException exc) {
      log.info(
          &quot;Failed to find a bean of class {}, message {}&quot;, key.getSimpleName(), exc.getMessage());
    }

    if (bean == null) {
      try {
        log.info(&quot;Creating a new validator bean of class &quot; + key.getSimpleName());
        bean = this.beanFactory.createBean(key);
      } catch (BeansException exc) {
        log.info(&quot;Failed to create a validator of class &quot; + key.getSimpleName());
      }
    }

    if (bean == null) {
      log.warn(&quot;Failed to get validator of class &quot; + key.getSimpleName());
    }

    return bean;
  }

  @Override
  public void releaseInstance(ConstraintValidator&lt;?, ?&gt; instance) {}
}

So it tries to find a Spring bean by a validator name. So I have an implementation of my validator:

@Component
public class UniqueUsernameValidatorImpl implements UniqueUsernameValidator {
  @Autowired private UserCredentialsRepository repository;

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    return repository.findByUsername(value).isEmpty();
  }
}

A validator interface:

public interface UniqueUsernameValidator extends ConstraintValidator&lt;UniqueUsername, String&gt; {

}

A constraint annotation:

@Target({
  ElementType.METHOD,
  ElementType.FIELD,
  ElementType.ANNOTATION_TYPE,
  ElementType.CONSTRUCTOR,
  ElementType.PARAMETER,
  ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(UniqueUsername.List.class)
@Documented
@Constraint(validatedBy = {UniqueUsernameValidator.class})
public @interface UniqueUsername {
  String message() default &quot;username must be unique&quot;;

  Class&lt;?&gt;[] groups() default {};

  Class&lt;? extends Payload&gt;[] payload() default {};

  @Target({
    ElementType.METHOD,
    ElementType.FIELD,
    ElementType.ANNOTATION_TYPE,
    ElementType.CONSTRUCTOR,
    ElementType.PARAMETER,
    ElementType.TYPE_USE
  })
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  @interface List {
    UniqueUsername[] value();
  }
}

It looks like Spring validation somehow manage to use my implementation of a validator, but then a Hibernate fails to instantiate a validator(?). I don't fully understand why there are 2 different initializations of validation mechanisms.

Is there a way to "register" or "inform" Hibernate validation to use my implementation of my custom validation annotation?

答案1

得分: 1

Hibernate Validator正在使用默认的ConstraintValidatorFactory实现(请查看堆栈跟踪中的ConstraintValidatorFactoryImpl),这意味着您的自定义实现未被正确考虑。

该问题出现在方法验证中,可能您需要提供一个ExecutableValidator bean。

(Hibernate Validator负责人在此,我不太清楚Spring的内部连接方式)。

英文:

Hibernate Validator is using the default impl of ConstraintValidatorFactory (see ConstraintValidatorFactoryImpl in your stacktrace) so that means your custom one is not taken into account somehow.

It's failing in the method validation, maybe you need to provide an ExecutableValidator bean?

(Hibernate Validator lead here, I don't know exactly how Spring wired things).

huangapple
  • 本文由 发表于 2020年10月19日 22:51:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/64429834.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定