| Let's say we have a HibernateConstraintValidator:
import javax.validation.ConstraintValidatorContext;
import javax.validation.metadata.ConstraintDescriptor;
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidator;
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext;
public class SomeHibernateConstraintValidator implements HibernateConstraintValidator<SomeHibernateConstraintValidatorConstraint, String> {
Foo payload;
@Override
public void initialize(ConstraintDescriptor<SimpleHibernateConstraintValidatorConstraint> constraintDescriptor,
HibernateConstraintValidatorInitializationContext initializationContext) {
initializationContext.getScriptEvaluatorForLanguage("someLanguageName");
initializationContext.getClockProvider();
initializationContext.getTemporalValidationTolerance();
payload = initializationContext.getConstraintValidatorPayload(Foo.class);
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
doSomething(this.payload);
}
}
Alright. Now we use that SomeHibernateConstraintValidator by creating different Validators with different (initialization)Contexts from the same ValidatorFactory:
final HibernateValidatorFactory hvf = someValidatorFactory.unwrap(HibernateValidatorFactory.class);
hvf.usingContext()
.temporalValidationTolerance(Duration.ofDays(5))
.getValidator().validate(...);
hvf.usingContext()
.temporalValidationTolerance(Duration.ofDays(10))
.getValidator().validate(...);
You see the problem I described in the comments. The same applies to the .scriptEvaluatorFactory(...) and .scriptEvaluatorFactory(...) methods. So the issue here is that the members of a HibernateConstraintValidatorInitializationContext instance are not taken into account when caching a HibernateConstraintValidator. The solution is that these three members should be part of the caching key when caching a constraint validator. For .constraintValidatorPayload(...) however (the fourth member of a HibernateConstraintValidatorInitializationContext) it's a different story. First of all who knows what the payload object is and/or what it references? And how many different payload object there will be during a validator factory lifetime (which usually is the app lifetime). In our case (web application) we create a new, different payload object for EACH request - plus such a payload is only be needed for the corresponding request (lifetime). So if we cache each HibernateConstraintValidator instance (in {{ConstraintValidatorManager}) that would mean if we have thousands of requests per second, we put thousands of constraint validator instances in our cache - per second... And we don't even need them anymore! Sounds like a memory leak. Because the payload could be any object really but isn't a type/class provided by Hibernate Validator and therefore out of our control we don't know what it is, how big it is, what if references and how many there will be. So I propose to never ever cache a constraint validator that could save a reference to such a payload. This condition applies if a) a payload is supplied in the current context (is !=null) *AND* b) only instances of HibernateConstraintValidatorInitializationContext - because only that classes get a payload passed via their initialize(...) method. |