Change By: Gunnar Morling (17/Dec/12 2:18 PM)
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.

{code:title=ConstraintValidatorManager.java|borderStyle=solid}
ConstraintValidator<A, V> constraintValidator = (ConstraintValidator<A, V>) constraintValidatorCache.get( key );
{code}

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.
{code:title=ConstraintValidatorManager$CacheKey.java|borderStyle=solid}
if ( annotation != null ? !annotation.equals( cacheKey.annotation ) : cacheKey.annotation != null ) {
    return false;
}
{code}

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.
{code:title=AnnotationProxy.java|borderStyle=solid}
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 );
}
{code}

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.

{code:title=Test.java|borderStyle=solid}
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));
{code}
The call annotation.equals(annotation) always returns false.
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators
For more information on JIRA, see: http://www.atlassian.com/software/jira