Dependency injection using Guice within Constraint

2019-07-20 17:38发布

I have a use case of injecting a class inside an implementation of ConstraintValidator. I'm using Google guice for Dependency injection and am currently unable to inject inside the validator.

Simplified form of my scenario

Inside Module:

@Provides
@Singleton
public ServiceA getServiceA() {
        return new ServiceA();
}

The constraint validator:

public class MyValidator implements ConstraintValidator<ValidS,List<String>> {

    private final ServiceA serviceA;

    @Inject
    public MyValidator(ServiceA serviceA) {
         this.serviceA = serviceA;
    }
    @Override
    public void initialize(final ValidS validS) {
    }

    @Override
    public boolean isValid(final List<String> sList, final ConstraintValidatorContext constraintValidatorContext) {
        System.out.println(serviceA.testInjection());
        //validation code
    }


}

Edit: Adding the exception message:

HV000064: Unable to instantiate ConstraintValidator: class com.validation.MyValidator.
javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator: class com.validation.MyValidator.
    at org.hibernate.validator.internal.util.privilegedactions.NewInstance.run(NewInstance.java:51) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.util.ReflectionHelper.run(ReflectionHelper.java:671) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.util.ReflectionHelper.newInstance(ReflectionHelper.java:219) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl.getInstance(ConstraintValidatorFactoryImpl.java:34) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager.createAndInitializeValidator(ConstraintValidatorManager.java:141) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager.getInitializedValidator(ConstraintValidatorManager.java:101) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:125) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:91) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:85) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraint(ValidatorImpl.java:478) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:424) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:388) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:340) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:158) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
    at com.MyClass.validate(MyClass.java:48) 

1条回答
等我变得足够好
2楼-- · 2019-07-20 18:31

Your custom validator cannot be instantiated by Hibernate, as Hibernate expects the validator to have a no arg constructor (hibernate docs):

The default ConstraintValidatorFactory provided by Hibernate Validator requires a public no-arg constructor to instantiate ConstraintValidator instances (see The constraint validator).

So you basically have two possibilities:

  1. Implement a custom ConstraintValidatorFactory, this is described in the linked documentation, and in that factory, get access to the guice injector to get the ServiceA and pass it to the constructor.

  2. Remove the public MyValidator(ServiceA serviceA) constructor and try to initialize the reference to ServiceA from within the public void initialize(final ValidS validS)method. Here you need to get the guice injector as well.

To get the injector you could do the following (this is no nice code, but somehow you need to get access to the injector). To store your injector reference you could use a Singleton - here I implement it by using an enum:

public enum GlobalInjector {
  INSTANCE;
  private Injector injector;
  public Injector getInjector() {
    return injector;
  }
  public void setInjector(Injector injector) {
    this.injector = injector;
  }      
}

I do not know how you initialize guice, but you probably have some code like this, so add the code to store the injector:

Injector injector = Guice.createInjector(new ConfigurationModule());
GlobalInjector.INSTANCE.setInjector(injector);

Then your initialize method would look like:

@Override
public void initialize(final ValidS validS) {
  Injector injector = GlobalInjector.INSTANCE.getInjector();
  if(null != injector) {
    serviceA = injector.getInstance(ServiceA.class);
  }
}

Do not forget to check if GlobalInjector.INSTANCE.getInjector(); returns not null, and when your validate method is called, make sure that serviceA is not null.

Note: personally I do not like the idea of storing the injector as a global variable (disguised by a singleton), but at the moment, I have no better idea. This code is not tested, so I hope it all works as intended.

查看更多
登录 后发表回答