From 09218a341eba66a564a30bd534abf6fd9f3dd575 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Wed, 10 May 2023 16:23:26 +0800 Subject: [PATCH] @Bean methods should be detected even no one declared locally with lite mode ConfigurationClassUtils should check bean methods in super classes also Fix GH-30449 --- .../annotation/ConfigurationClassParser.java | 2 +- .../ConfigurationClassPostProcessor.java | 2 +- .../annotation/ConfigurationClassUtils.java | 36 ++++++++++++++++--- .../ConfigurationClassPostProcessorTests.java | 23 +++++++++++- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index afcc46a27f24..ee9137b7c42d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -349,7 +349,7 @@ private void processMemberClasses(ConfigurationClass configClass, SourceClass so if (!memberClasses.isEmpty()) { List candidates = new ArrayList<>(memberClasses.size()); for (SourceClass memberClass : memberClasses) { - if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) && + if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata(), this.metadataReaderFactory) && !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) { candidates.add(memberClass); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index f68c34ef0c46..87140ced56a1 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -490,7 +490,7 @@ public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFact // or component class without @Bean methods. boolean liteConfigurationCandidateWithoutBeanMethods = (ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) && - annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata)); + annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata, this.metadataReaderFactory)); if (!liteConfigurationCandidateWithoutBeanMethods) { try { abd.resolveBeanClass(this.beanClassLoader); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index acfd6f6c3bb1..674f25b8dc77 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -36,6 +36,7 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.lang.Nullable; @@ -48,6 +49,7 @@ * @author Juergen Hoeller * @author Sam Brannen * @author Stephane Nicoll + * @author Yanming Zhou * @since 6.0 */ public abstract class ConfigurationClassUtils { @@ -71,6 +73,7 @@ public abstract class ConfigurationClassUtils { Import.class.getName(), ImportResource.class.getName()); + private static final MetadataReaderFactory defaultMetadataReaderFactory = new CachingMetadataReaderFactory(); /** * Initialize a configuration class proxy for the specified class. @@ -136,7 +139,7 @@ else if (beanDef instanceof AbstractBeanDefinition abstractBd && abstractBd.hasB if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } - else if (config != null || isConfigurationCandidate(metadata)) { + else if (config != null || isConfigurationCandidate(metadata, metadataReaderFactory)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { @@ -160,6 +163,18 @@ else if (config != null || isConfigurationCandidate(metadata)) { * configuration class processing; {@code false} otherwise */ static boolean isConfigurationCandidate(AnnotationMetadata metadata) { + return isConfigurationCandidate(metadata, defaultMetadataReaderFactory); + } + + /** + * Check the given metadata for a configuration class candidate + * (or nested component class declared within a configuration/component class). + * @param metadata the metadata of the annotated class + * @param metadataReaderFactory the current factory in use by the caller + * @return {@code true} if the given class is to be registered for + * configuration class processing; {@code false} otherwise + */ + static boolean isConfigurationCandidate(AnnotationMetadata metadata, MetadataReaderFactory metadataReaderFactory) { // Do not consider an interface or an annotation... if (metadata.isInterface()) { return false; @@ -173,19 +188,30 @@ static boolean isConfigurationCandidate(AnnotationMetadata metadata) { } // Finally, let's look for @Bean methods... - return hasBeanMethods(metadata); + return hasBeanMethods(metadata, metadataReaderFactory); } - static boolean hasBeanMethods(AnnotationMetadata metadata) { + static boolean hasBeanMethods(AnnotationMetadata metadata, MetadataReaderFactory metadataReaderFactory) { try { - return metadata.hasAnnotatedMethods(Bean.class.getName()); + AnnotationMetadata md = metadata; + while (true) { + if (md.hasAnnotatedMethods(Bean.class.getName())) { + return true; + } + if (md.hasSuperClass()) { + md = metadataReaderFactory.getMetadataReader(md.getSuperClassName()).getAnnotationMetadata(); + } + else { + break; + } + } } catch (Throwable ex) { if (logger.isDebugEnabled()) { logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex); } - return false; } + return false; } /** diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index c21783c9c817..1268d5081985 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,11 +69,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatNoException; /** * @author Chris Beams * @author Juergen Hoeller * @author Sam Brannen + * @author Yanming Zhou */ class ConfigurationClassPostProcessorTests { @@ -1125,6 +1127,13 @@ void testBeanDefinitionRegistryPostProcessorConfig() { } + @Test + void testBeanNotDeclaredLocallyWithLiteMode() { // GH-30449 + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(LiteConfiguration.class); + assertThatNoException().isThrownBy(() -> ctx.getBean("myTestBean")); + ctx.close(); + } + // ------------------------------------------------------------------------- @Configuration @@ -2038,4 +2047,16 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) } } + static class LiteConfiguration extends BaseLiteConfiguration { + + } + + static class BaseLiteConfiguration { + @Bean + TestBean myTestBean() { + return new TestBean(); + } + + } + }