3

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.<init>()
    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(<generated>) ~[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 <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
    T bean = null;

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

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

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

    return bean;
  }

  @Override
  public void releaseInstance(ConstraintValidator<?, ?> 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<UniqueUsername, String> {

}

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 "username must be unique";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] 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?

zolv
  • 1,720
  • 2
  • 19
  • 36
  • Would you please put them here? log for your own classes. – Tashkhisi Oct 19 '20 at 15:41
  • I think in this portion of code Spring failed to `getBean` and throws an exception right? `try { log.info("Trying to find a validator bean of class " + key.getSimpleName()); bean = (T) this.beanFactory.getBean(key); } catch (BeansException exc) { log.info( "Failed to find a bean of class {}, message {}", key.getSimpleName(), exc.getMessage()); } ` – Tashkhisi Oct 19 '20 at 15:46
  • @tashkhisi For nonexisting beans - yes. But for the one I'm interested in - imlementation of `UsernameUniqueValidator` it works just fine. Spring finds this bean and executes the validation code. The problem is that later, hibernate fails to instantiate an interface (obviously) – zolv Oct 19 '20 at 20:50
  • What happen when you set this property? `spring.jpa.properties.javax.persistence.validation.mode=none` – Tashkhisi Oct 20 '20 at 08:02

1 Answers1

1

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

Guillaume Smet
  • 9,921
  • 22
  • 29