|
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.
|