How to avoid cross dependency between layers because of @Constraint validatedBy?
Asked Answered
L

3

12

In our project we have Service and DAO layers in separate Maven modules. Service module depends on DAO module and works with it's entities. The problem is that we can't put custom jsr 303 constraint which uses some services from service layer in DAO entity because that would create the back reference from DAO layer to service layer, because the validator class needs to be referenced in validatedBy attribute of custom @Constraint.

Is there a way (using standard jsr 303 api) to specify validator class of some custom constraint at runtime (or solve our problem in any other way)? The built in constraints have empty validatedBy attribute, but I don't know if there is some api for that.

Lucier answered 30/7, 2012 at 11:35 Comment(0)
P
5

You may use an XML based constraint mapping to assign a validator to your constraint to avoid the reference from the annotation to the validator implementation.

The BV 1.1 EG is also discussing some improvements in that area. Feel free to weigh in on the issue on the mailing list.

Parnassian answered 30/7, 2012 at 19:0 Comment(1)
When working with Hibernate Validator, you also can use the service loader mechanism to find constraint validators, avoiding any link from the constraint annotation type to its validator.Parnassian
H
14

We faced the same issue in our Spring based project. To solve it in best Spring way we split ConstraintValidator interface and implementation. For example in domain layer we only have interface:

public interface UniqueValidator extends ConstraintValidator<Unique, String> {
}

In service layer we implement that interface:

public class UniqueValidatorJpaImpl implements UniqueValidator {
    private EntityManager entityManager;
    ...
}

Next we declare a bean in Spring Context for UniqueValidatorJpaImpl.

Finally to make all that staff working we extended SpringConstraintValidatorFactory. By default it only creates a new instance of class specified in validatedBy. We extended it by first looking in spring context for a bean of corresponding type:

public class SpringConstraintValidatorFactoryEx implements ConstraintValidatorFactory {

    private final Logger logger = LoggerFactory.getLogger(SpringConstraintValidatorFactoryEx.class);

    @Autowired
    private AutowireCapableBeanFactory beanFactory;

    public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
        T bean = null;

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

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

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

        return bean;
    }

}
Hathcock answered 21/8, 2013 at 8:43 Comment(3)
Pure Java EE project here (no Spring), where a similar solution will work. Only somewhat easier: you just implement an interface with the validation method (no need to extend anything), implement it in the service layer, and inject it directly in the validator in the domain layer.Sequin
Pure Java EE project here (no Spring), where a similar solution will work. Only somewhat easier: you just implement an interface with the validation method (no need to extend anything), implement it in the service layer, and inject it directly in the validator in the domain layer.Sequin
@Hathcock Can You elaborate on bean Configuration? I still make it working. I've addded a configuration @Bean public UniqueUsernameValidator uniqueUsernameValidator() {return new UniqueUsernameValidatorImpl();} and also @Bean public ConstraintValidatorFactory constraintValidatorFactory() {return new SpringConstraintValidatorFactoryEx();} But it still doesn't work or I misunderstood something.Cloakroom
P
5

You may use an XML based constraint mapping to assign a validator to your constraint to avoid the reference from the annotation to the validator implementation.

The BV 1.1 EG is also discussing some improvements in that area. Feel free to weigh in on the issue on the mailing list.

Parnassian answered 30/7, 2012 at 19:0 Comment(1)
When working with Hibernate Validator, you also can use the service loader mechanism to find constraint validators, avoiding any link from the constraint annotation type to its validator.Parnassian
P
0

For Spring 5 you need to implement custom ConstraintValidatorFactory:

@Component
class CustomConstraintValidatorFactory(
  private val beanFactory: AutowireCapableBeanFactory
) : SpringConstraintValidatorFactory(beanFactory) {

  override fun <T : ConstraintValidator<*, *>> getInstance(key: Class<T>): T = this.beanFactory.getBean(key)

}

and create configuration for LocalValidatorFactoryBean:

@Configuration
class ValidationConfiguration(
  private val customConstraintValidatorFactory: CustomConstraintValidatorFactory
) {

  @Bean
  fun getValidator(): LocalValidatorFactoryBean {
    val bean = LocalValidatorFactoryBean()
    bean.constraintValidatorFactory = customConstraintValidatorFactory
    return bean
  }
}

Code in kotlin

Palomo answered 15/9, 2022 at 9:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.