We're updating the issue view to help you get more done. 

ConstraintValidatorManager caching fails for XML-defined constraints

Description

Heap analysis of my webapp showed that I have several thousand live instances of ConstraintValidatorManager$CacheKey, with the numbers growing with every subsequent request. This was surprising given that the application had only 3 constraints configured (via XML).

Hooking up the debugger, I could see that the cache lookup on line 92 of ConstraintValidatorManager always fails - even though the validator is already cached, a new instance is initialized and returned.

ConstraintValidatorManager.java

1 ConstraintValidator<A, V> constraintValidator = (ConstraintValidator<A, V>) constraintValidatorCache.get( key );

I set a breakpoint on the equals method of CacheKey, and found that it always returns false when comparing the annotation member of the two CacheKeys - even when both annotations refer to the same instance.

ConstraintValidatorManager$CacheKey.java

1 2 3 if ( annotation != null ? !annotation.equals( cacheKey.annotation ) : cacheKey.annotation != null ) { return false; }

The annotation in this case isn't a real annotation (because the constraints are defined in XML) - it's a JDK dynamic Proxy which uses the AnnotationProxy class as its InvocationHandler.

AnnotationProxy.java

1 2 3 4 5 6 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ( values.containsKey( method.getName() ) ) { return values.get( method.getName() ); } return method.invoke( this, args ); }

When equals() is called via the dynamic Proxy, the eventual invocation is a call to Object.equals(). The problem is that the left-hand argument is this - an AnnotationProxy instance, and the right-hand argument is a dynamic Proxy instance. Object.equals() therefore always returns false.

This simple piece of code shows the broken call to equals() clearly.

Test.java

1 2 3 4 5 6 7 8 9 10 11 12 13 AnnotationDescriptor<DecimalMin> descriptor = new AnnotationDescriptor<DecimalMin>(DecimalMin.class); descriptor.setValue("message", "some message"); descriptor.setValue("value", "1"); AnnotationProxy proxy = new AnnotationProxy(descriptor); Annotation annotation = (Annotation)Proxy.newProxyInstance( DecimalMin.class.getClassLoader(), new Class[] { DecimalMin.class }, proxy); System.out.println(annotation.equals(annotation));

The call annotation.equals(annotation) always returns false.

Environment

hibernate-validator 5.0.0.Alpha1
Java 6

Status

Assignee

Gunnar Morling

Reporter

Craig Leonard

Labels

Worked in

None

Feedback Requested

None

Feedback Requested By

None

backPortable

None

Community Help Wanted

None

Suitable for new contributors

None

Requires Release Note

None

Pull Request

None

backportDecision

None

backportReEvaluate

None

Components

Fix versions

Affects versions

5.0.0.Alpha1

Priority

Major