Skip annotations that cannot be processed in AnnotationBeanNameGenerator

Prior to this commit, AnnotationBeanNameGenerator failed when searching
for a convention-based bean name, if an annotation referenced a
non-existent class.

To address that, this commit introduces a try-catch block around each
invocation of MergedAnnotation.asAnnotationAttributes() and skips
processing of the current MergedAnnotation if an exception occurs,
which is likely due to a type referenced from an annotation attribute
not being present in the classpath.

See gh-31203
Closes gh-36524
This commit is contained in:
Sam Brannen
2026-04-02 16:51:17 +02:00
parent d87ebbff78
commit 00fbd91cca
2 changed files with 65 additions and 1 deletions
@@ -139,7 +139,17 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
Set<AnnotationAttributes> visited = new HashSet<>();
for (MergedAnnotation<Annotation> mergedAnnotation : mergedAnnotations) {
AnnotationAttributes attributes = mergedAnnotation.asAnnotationAttributes(ADAPTATIONS);
AnnotationAttributes attributes = null;
try {
attributes = mergedAnnotation.asAnnotationAttributes(ADAPTATIONS);
}
catch (Throwable ex) {
// Ignore exception and current MergedAnnotation, assuming that values of the
// MergedAnnotation could not be adapted to a Map/AnnotationAttributes due to
// missing types referenced via annotation attributes.
continue;
}
if (visited.add(attributes)) {
String annotationType = mergedAnnotation.getType().getName();
Set<String> metaAnnotationTypes = this.metaAnnotationTypesCache.computeIfAbsent(annotationType,
@@ -31,7 +31,12 @@ import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefiniti
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry;
import org.springframework.core.OverridingClassLoader;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
@@ -91,6 +96,14 @@ class AnnotationBeanNameGeneratorTests {
"myComponent", "myService");
}
@Test // gh-gh-36524
void generateBeanNameForConventionBasedComponentWithMissingAnnotationAttributeTypeViaAsm() throws Exception {
FilteringClassLoader classLoader = new FilteringClassLoader(getClass().getClassLoader());
MetadataReaderFactory readerFactory = new SimpleMetadataReaderFactory(classLoader);
MetadataReader reader = readerFactory.getMetadataReader(ConventionBasedComponentWithMissingAnnotationAttributeType.class.getName());
assertGeneratedName(reader.getAnnotationMetadata(), "myComponent");
}
@Test
void generateBeanNameForComponentWithConflictingNames() {
BeanDefinition bd = annotatedBeanDef(ComponentWithMultipleConflictingNames.class);
@@ -171,6 +184,11 @@ class AnnotationBeanNameGeneratorTests {
assertThat(generateBeanName(bd)).isNotBlank().isEqualTo(expectedName);
}
private void assertGeneratedName(AnnotationMetadata annotationMetadata, String expectedName) {
BeanDefinition bd = new AnnotatedGenericBeanDefinition(annotationMetadata);
assertThat(generateBeanName(bd)).isNotBlank().isEqualTo(expectedName);
}
private void assertGeneratedNameIsDefault(Class<?> clazz) {
BeanDefinition bd = annotatedBeanDef(clazz);
String expectedName = this.beanNameGenerator.buildDefaultBeanName(bd);
@@ -230,6 +248,22 @@ class AnnotationBeanNameGeneratorTests {
static class ConventionBasedComponentWithMultipleConflictingNames {
}
static class FilteredType {
}
@Retention(RetentionPolicy.RUNTIME)
@interface ExampleAnnotation {
Class<?> value() default Void.class;
String description() default "";
}
@ExampleAnnotation(value = FilteredType.class, description = "optional")
@ConventionBasedComponent1("myComponent")
static class ConventionBasedComponentWithMissingAnnotationAttributeType {
}
@Component
private static class AnonymousComponent {
}
@@ -376,4 +410,24 @@ class AnnotationBeanNameGeneratorTests {
static class StereotypeWithGeneratedName {
}
static class FilteringClassLoader extends OverridingClassLoader {
FilteringClassLoader(ClassLoader parent) {
super(parent);
}
@Override
protected boolean isEligibleForOverriding(String className) {
return className.startsWith(AnnotationBeanNameGeneratorTests.class.getName());
}
@Override
protected Class<?> loadClassForOverriding(String name) throws ClassNotFoundException {
if (name.contains("Filtered")) {
throw new ClassNotFoundException(name);
}
return super.loadClassForOverriding(name);
}
}
}