From 5bbeadec0c80591b52602eb6f87aa00ecb9cc096 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 29 May 2018 21:47:53 +0200 Subject: [PATCH] Detect nested configuration classes even for empty outer classes Issue: SPR-16839 --- .../annotation/ConfigurationClassUtils.java | 43 +++++++++- .../ImportVersusDirectRegistrationTests.java | 78 +++++++++++++++++++ 2 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 spring-context/src/test/java/org/springframework/context/annotation/ImportVersusDirectRegistrationTests.java 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 4d579f653a5..795a7bf05ae 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 @@ -78,7 +78,9 @@ abstract class ConfigurationClassUtils { * @param metadataReaderFactory the current factory in use by the caller * @return whether the candidate qualifies as (any kind of) configuration class */ - public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { + public static boolean checkConfigurationClassCandidate( + BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { + String className = beanDef.getBeanClassName(); if (className == null || beanDef.getFactoryMethodName() != null) { return false; @@ -103,7 +105,8 @@ abstract class ConfigurationClassUtils { } catch (IOException ex) { if (logger.isDebugEnabled()) { - logger.debug("Could not find class file for introspecting configuration annotations: " + className, ex); + logger.debug("Could not find class file for introspecting configuration annotations: " + + className, ex); } return false; } @@ -116,7 +119,7 @@ abstract class ConfigurationClassUtils { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { - return false; + return hasNestedConfigurationClass(metadata, metadataReaderFactory); } // It's a full or lite configuration candidate... Let's determine the order value, if any. @@ -128,6 +131,40 @@ abstract class ConfigurationClassUtils { return true; } + /** + * Check whether the specified class declares a nested configuration class. + */ + private static boolean hasNestedConfigurationClass( + AnnotationMetadata metadata, MetadataReaderFactory metadataReaderFactory) { + + // Potentially nested configuration classes... + if (metadata instanceof StandardAnnotationMetadata) { + Class beanClass = ((StandardAnnotationMetadata) metadata).getIntrospectedClass(); + for (Class memberClass : beanClass.getDeclaredClasses()) { + if (isConfigurationCandidate(new StandardAnnotationMetadata(memberClass))) { + return true; + } + } + } + else { + for (String memberName : metadata.getMemberClassNames()) { + try { + MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(memberName); + if (isConfigurationCandidate(metadataReader.getAnnotationMetadata())) { + return true; + } + } + catch (IOException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Could not find class file for introspecting configuration annotations: " + + memberName, ex); + } + } + } + } + return false; + } + /** * Check the given metadata for a configuration class candidate * (or nested component class declared within a configuration/component class). diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ImportVersusDirectRegistrationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ImportVersusDirectRegistrationTests.java new file mode 100644 index 00000000000..4b471e4d14e --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/ImportVersusDirectRegistrationTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2018 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import org.junit.Test; + +import org.springframework.beans.factory.support.RootBeanDefinition; + +/** + * @author Andy Wilkinson + */ +public class ImportVersusDirectRegistrationTests { + + @Test + public void thingIsAvailableWhenOuterConfigurationIsRegisteredDirectly() { + try (AnnotationConfigApplicationContext directRegistration = new AnnotationConfigApplicationContext()) { + directRegistration.register(AccidentalLiteConfiguration.class); + directRegistration.refresh(); + directRegistration.getBean(Thing.class); + } + } + + @Test + public void thingIsAvailableWhenOuterConfigurationIsRegisteredWithClassName() { + try (AnnotationConfigApplicationContext directRegistration = new AnnotationConfigApplicationContext()) { + directRegistration.registerBeanDefinition("config", + new RootBeanDefinition(AccidentalLiteConfiguration.class.getName())); + directRegistration.refresh(); + directRegistration.getBean(Thing.class); + } + } + + @Test + public void thingIsAvailableWhenOuterConfigurationIsImported() { + try (AnnotationConfigApplicationContext viaImport = new AnnotationConfigApplicationContext()) { + viaImport.register(Importer.class); + viaImport.refresh(); + viaImport.getBean(Thing.class); + } + } + +} + + +@Import(AccidentalLiteConfiguration.class) +class Importer { +} + + +class AccidentalLiteConfiguration { + + @Configuration + class InnerConfiguration { + + @Bean + public Thing thing() { + return new Thing(); + } + } +} + + +class Thing { +}