Skip to content

Unsafe invocation of .value() on annotations in SynthesizedMergedAnnotationInvocationHandler prevents backwards-compatible additions to @annotation interfaces #24029

@olaf-otto

Description

@olaf-otto

Context

I have a scenario where I am extending an annotation API from

@interface MyAnnotation {
    String[] old();
}

to

@interface MyAnnotation {
    String[] value();

   @deprecated
    String[] old() default {}
}

Here, old libraries compiled with the old annotation are still supported as the change is binary compatible. It is only that I as a framework author need to handle the case of an invocation of value() throwing an IncompleteAnnotationException.

I believe allowing this is crucial to evolution of annotations without forcing all implementors to re-compile.

Issue in Spring Core

When calling org.springframework.beans.factory.ListableBeanFactory#findAnnotationOnBean, Spring tries to build a proxy for the found annotation. In the process, spring attempts to load the value of all annotation attributes via org.springframework.core.annotation.SynthesizedMergedAnnotationInvocationHandler#getAttributeValue. This fails for the newly added annotation attribute as mentioned above:

java.lang.annotation.IncompleteAnnotationException: MyAnnotation missing element value
	at sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:81)
	at com.sun.proxy.$Proxy180.value(Unknown Source)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:279) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:263) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.core.annotation.TypeMappedAnnotation.getValue(TypeMappedAnnotation.java:429) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.core.annotation.TypeMappedAnnotation.getValue(TypeMappedAnnotation.java:399) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.core.annotation.TypeMappedAnnotation.getAttributeValue(TypeMappedAnnotation.java:384) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.core.annotation.AbstractMergedAnnotation.getValue(AbstractMergedAnnotation.java:178) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.core.annotation.SynthesizedMergedAnnotationInvocationHandler.getAttributeValue(SynthesizedMergedAnnotationInvocationHandler.java:176) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.core.annotation.SynthesizedMergedAnnotationInvocationHandler.<init>(SynthesizedMergedAnnotationInvocationHandler.java:66) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.core.annotation.SynthesizedMergedAnnotationInvocationHandler.createProxy(SynthesizedMergedAnnotationInvocationHandler.java:184) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.core.annotation.TypeMappedAnnotation.createSynthesized(TypeMappedAnnotation.java:335) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.core.annotation.AbstractMergedAnnotation.synthesize(AbstractMergedAnnotation.java:210) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.core.annotation.AbstractMergedAnnotation.synthesize(AbstractMergedAnnotation.java:200) [org.apache.servicemix.bundles.spring-core:5.2.0.RELEASE_1]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAnnotationOnBean(DefaultListableBeanFactory.java:680) [io.neba.spring-beans:5.2.0.RELEASE_1]

Here, I believe the implementation should not expected values to be complete. I am not even sure as to why org.springframework.core.annotation.SynthesizedMergedAnnotationInvocationHandler#SynthesizedMergedAnnotationInvocationHandler loads the values in the first place.

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: regressionA bug that is also a regression

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions