Affects: \spring-beans 6.1.+ (likely newer versions as well)
Overview
We have a custom annotation that we used to attach as qualifier to bean definitions through a post processor. When we added a new String[]
array to that annotation, beans no longer matched. This is due to QualifierAnnotationAutowireCandidateResolver.checkQualifier
eventually doing if (!expectedValue.equals(actualValue)) {
which fails for arrays. The equals
methods on arrays purely does identity rather than deep equality of the items.
Deep-Dive
We define a custom annotation as:
@Inherited
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
@Autowired
public @interface MyAnnotation {
String value();
String[] inherits() default { };
}
We create custom bean definitions via:
MyAnnotation annotation = getAnnnotation(); // this is handled elsewhere
RootBeanDefinition rbd = (RootBeanDefinition) BeanDefinitionBuilder.rootBeanDefinition(MyFactoryBean.class)
getBeanDefinition();
AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(MyAnnotation.class, annotation.value());
qualifier.setAttribute("inherits", annotation.inherits());
rbd.addQualifier(qualifier);
registry.registerBeanDefinition(beanName, rbd);
We then try to inject this bean via:
@Component
public class MyClass {
public MyClass(@MyAnnotation(value = "foo", inherits = "bar") MyClass myClass) {
....
}
}
This works fine with inherits
is not on the annotation, but fails when the array is added.
Eventually this calls QualifierAnnotationAutowireCandidateResolver.checkQualifier
. This fetches the qualifier from the bean definition per the post-processor. It then fetches AnnotationUtils.getAnnotationAttributes(annotation);
to get all the properties of the target in MyClass
(ie: @MyAnnotation(value = "foo", inherits = "bar")
). It traverses each property and compares against the qualifier attributes. That eventually results in getting the string array from the qualifier attributes and the annotation property. Those will always end up being different identities since annotations return a new value each time. Even if not, because of how we set the attributes, they would be. It then calls if (!expectedValue.equals(actualValue)) {
which fails since arrays use identity comparison not deep equality checks of each item.
Would it make sense here to do a comparison checks that is array-aware and then compare the items rather than purely identity?
We ended up finding a better workaround where we use rbd.setQualifiedElement
and copy the annotation into that. This bypasses the qualifier checks and just compares the annotation directly which works as expected.