diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java b/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java index 27d16b727788..8ad41b280fbc 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java @@ -44,12 +44,20 @@ * class will be subject to the conditions. * *
NOTE: Inheritance of {@code @Conditional} annotations - * is not supported; any conditions from superclasses or from overridden - * methods will not be considered. In order to enforce these semantics, - * {@code @Conditional} itself is not declared as - * {@link java.lang.annotation.Inherited @Inherited}; furthermore, any - * custom composed annotation that is meta-annotated with - * {@code @Conditional} must not be declared as {@code @Inherited}. + * from superclasses or from overridden methods is not supported. To enforce + * these semantics, {@code @Conditional} is not declared as + * {@link java.lang.annotation.Inherited @Inherited}, and any custom + * composed annotation meta-annotated with {@code @Conditional} + * must not be declared as {@code @Inherited} either. + * + *
Conditions declared on a lexically enclosing class, however, do gate
+ * registration of any nested static {@code @Configuration} classes within it.
+ * This applies regardless of how the nested class is discovered: as a member
+ * of its enclosing class, via {@link Import @Import}, via
+ * {@link ComponentScan @ComponentScan}, or through direct registration.
+ * The single exception is a nested class imported via {@code @Import} from
+ * outside its enclosing class, in which case only the importer's conditions
+ * apply.
*
* @author Phillip Webb
* @author Sam Brannen
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
index a5f64e71afea..5bd98227bc4c 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
@@ -16,6 +16,7 @@
package org.springframework.context.annotation;
+import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
@@ -54,6 +55,7 @@
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.StandardMethodMetadata;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
@@ -95,6 +97,8 @@ class ConfigurationClassBeanDefinitionReader {
private final ConditionEvaluator conditionEvaluator;
+ private final MetadataReaderFactory metadataReaderFactory;
+
/**
* Create a new {@link ConfigurationClassBeanDefinitionReader} instance
@@ -102,7 +106,7 @@ class ConfigurationClassBeanDefinitionReader {
*/
ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
ResourceLoader resourceLoader, Environment environment, BeanNameGenerator importBeanNameGenerator,
- ImportRegistry importRegistry) {
+ ImportRegistry importRegistry, MetadataReaderFactory metadataReaderFactory) {
this.registry = registry;
this.sourceExtractor = sourceExtractor;
@@ -111,6 +115,7 @@ class ConfigurationClassBeanDefinitionReader {
this.importBeanNameGenerator = importBeanNameGenerator;
this.importRegistry = importRegistry;
this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
+ this.metadataReaderFactory = metadataReaderFactory;
}
@@ -524,11 +529,34 @@ public boolean shouldSkip(ConfigurationClass configClass) {
}
if (skip == null) {
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
+ if (!skip && !configClass.isImported()) {
+ // Non-imported nested @Configuration: also honor REGISTER_BEAN-phase
+ // conditions declared on any lexically enclosing class.
+ skip = shouldSkipFromEnclosingClasses(configClass.getMetadata());
+ }
}
this.skipped.put(configClass, skip);
}
return skip;
}
+
+ private boolean shouldSkipFromEnclosingClasses(AnnotationMetadata metadata) {
+ String enclosingClassName = metadata.getEnclosingClassName();
+ while (enclosingClassName != null) {
+ AnnotationMetadata enclosing;
+ try {
+ enclosing = metadataReaderFactory.getMetadataReader(enclosingClassName).getAnnotationMetadata();
+ }
+ catch (IOException ex) {
+ return false;
+ }
+ if (conditionEvaluator.shouldSkip(enclosing, ConfigurationPhase.REGISTER_BEAN)) {
+ return true;
+ }
+ enclosingClassName = enclosing.getEnclosingClassName();
+ }
+ return false;
+ }
}
}
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 782bbe8ab991..b50423bb35a5 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
@@ -249,6 +249,10 @@ protected void processConfigurationClass(ConfigurationClass configClass, Predica
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
+ if (!configClass.isImported() && shouldSkipFromEnclosingClasses(
+ configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
+ return;
+ }
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
@@ -735,6 +739,13 @@ private List Prior to this behavior, a nested {@code @Configuration} discovered through
+ * an independent path (component scan or direct registration) was processed using
+ * only its own metadata, silently bypassing any condition declared on its lexical
+ * enclosing class.
+ */
+class EnclosingConditionConfigurationTests {
+
+ @Test
+ void scanDiscoveredInnerSkipsWhenEnclosingParseConditionFails() {
+ try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ ScannerForParseDisabledOuter.class)) {
+ assertThat(context.containsBean("innerBean")).isFalse();
+ }
+ }
+
+ @Test
+ void scanDiscoveredInnerSkipsWhenEnclosingRegisterBeanConditionFails() {
+ try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ ScannerForRegisterBeanDisabledOuter.class)) {
+ assertThat(context.containsBean("innerBean")).isFalse();
+ }
+ }
+
+ @Test
+ void scanDiscoveredInnerIsRegisteredWhenEnclosingConditionMatches() {
+ try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ ScannerForEnabledOuter.class)) {
+ assertThat(context.containsBean("innerBean")).isTrue();
+ }
+ }
+
+ @Test
+ void directlyRegisteredInnerSkipsWhenEnclosingParseConditionFails() {
+ try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
+ context.register(ParseDisabledOuter.Inner.class);
+ context.refresh();
+ assertThat(context.containsBean("innerBean")).isFalse();
+ }
+ }
+
+ @Test
+ void deeplyNestedInnerSkipsWhenOutermostParseConditionFails() {
+ try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ ScannerForDeeplyNestedDisabledOuter.class)) {
+ assertThat(context.containsBean("deepInnerBean")).isFalse();
+ }
+ }
+
+ @Test
+ void scanDiscoveredInnerSkipsWhenEnclosingInterfaceConditionFails() {
+ try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ ScannerForInterfaceEnclosingDisabled.class)) {
+ assertThat(context.containsBean("innerBean")).isFalse();
+ }
+ }
+
+ @Test
+ void scanDiscoveredInnerSkipsWhenEnclosingFailsEvenIfInnerOwnConditionMatches() {
+ try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ ScannerForMixedPhaseOuterFails.class)) {
+ assertThat(context.containsBean("innerBean")).isFalse();
+ }
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @ComponentScan(
+ basePackageClasses = ParseDisabledOuter.class,
+ useDefaultFilters = false,
+ includeFilters = @Filter(Configuration.class),
+ resourcePattern = "EnclosingConditionConfigurationTests$ParseDisabledOuter*.class")
+ static class ScannerForParseDisabledOuter {
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @ComponentScan(
+ basePackageClasses = RegisterBeanDisabledOuter.class,
+ useDefaultFilters = false,
+ includeFilters = @Filter(Configuration.class),
+ resourcePattern = "EnclosingConditionConfigurationTests$RegisterBeanDisabledOuter*.class")
+ static class ScannerForRegisterBeanDisabledOuter {
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @ComponentScan(
+ basePackageClasses = EnabledOuter.class,
+ useDefaultFilters = false,
+ includeFilters = @Filter(Configuration.class),
+ resourcePattern = "EnclosingConditionConfigurationTests$EnabledOuter*.class")
+ static class ScannerForEnabledOuter {
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @ComponentScan(
+ basePackageClasses = DeeplyNestedDisabledOuter.class,
+ useDefaultFilters = false,
+ includeFilters = @Filter(Configuration.class),
+ resourcePattern = "EnclosingConditionConfigurationTests$DeeplyNestedDisabledOuter*.class")
+ static class ScannerForDeeplyNestedDisabledOuter {
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @ComponentScan(
+ basePackageClasses = InterfaceEnclosingDisabled.class,
+ useDefaultFilters = false,
+ includeFilters = @Filter(Configuration.class),
+ resourcePattern = "EnclosingConditionConfigurationTests$InterfaceEnclosingDisabled*.class")
+ static class ScannerForInterfaceEnclosingDisabled {
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @ComponentScan(
+ basePackageClasses = MixedPhaseOuterFails.class,
+ useDefaultFilters = false,
+ includeFilters = @Filter(Configuration.class),
+ resourcePattern = "EnclosingConditionConfigurationTests$MixedPhaseOuterFails*.class")
+ static class ScannerForMixedPhaseOuterFails {
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @Conditional(NeverMatchParseCondition.class)
+ static class ParseDisabledOuter {
+
+ @Configuration(proxyBeanMethods = false)
+ static class Inner {
+
+ @Bean
+ String innerBean() {
+ return "inner";
+ }
+ }
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @Conditional(NeverMatchRegisterBeanCondition.class)
+ static class RegisterBeanDisabledOuter {
+
+ @Configuration(proxyBeanMethods = false)
+ static class Inner {
+
+ @Bean
+ String innerBean() {
+ return "inner";
+ }
+ }
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @Conditional(AlwaysMatchCondition.class)
+ static class EnabledOuter {
+
+ @Configuration(proxyBeanMethods = false)
+ static class Inner {
+
+ @Bean
+ String innerBean() {
+ return "inner";
+ }
+ }
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @Conditional(NeverMatchParseCondition.class)
+ static class DeeplyNestedDisabledOuter {
+
+ @Configuration(proxyBeanMethods = false)
+ static class Middle {
+
+ @Configuration(proxyBeanMethods = false)
+ static class DeepInner {
+
+ @Bean
+ String deepInnerBean() {
+ return "deep";
+ }
+ }
+ }
+ }
+
+
+ @Conditional(NeverMatchParseCondition.class)
+ interface InterfaceEnclosingDisabled {
+
+ @Configuration(proxyBeanMethods = false)
+ class Inner {
+
+ @Bean
+ String innerBean() {
+ return "inner";
+ }
+ }
+ }
+
+
+ @Configuration(proxyBeanMethods = false)
+ @Conditional(NeverMatchRegisterBeanCondition.class)
+ static class MixedPhaseOuterFails {
+
+ @Configuration(proxyBeanMethods = false)
+ @Conditional(AlwaysMatchCondition.class)
+ static class Inner {
+
+ @Bean
+ String innerBean() {
+ return "inner";
+ }
+ }
+ }
+
+
+ static class NeverMatchParseCondition implements ConfigurationCondition {
+
+ @Override
+ public boolean matches(@NonNull ConditionContext context, @NonNull AnnotatedTypeMetadata metadata) {
+ return false;
+ }
+
+ @Override
+ public @NonNull ConfigurationPhase getConfigurationPhase() {
+ return ConfigurationPhase.PARSE_CONFIGURATION;
+ }
+ }
+
+
+ static class NeverMatchRegisterBeanCondition implements ConfigurationCondition {
+
+ @Override
+ public boolean matches(@NonNull ConditionContext context, @NonNull AnnotatedTypeMetadata metadata) {
+ return false;
+ }
+
+ @Override
+ public @NonNull ConfigurationPhase getConfigurationPhase() {
+ return ConfigurationPhase.REGISTER_BEAN;
+ }
+ }
+
+
+ static class AlwaysMatchCondition implements ConfigurationCondition {
+
+ @Override
+ public boolean matches(@NonNull ConditionContext context, @NonNull AnnotatedTypeMetadata metadata) {
+ return true;
+ }
+
+ @Override
+ public @NonNull ConfigurationPhase getConfigurationPhase() {
+ return ConfigurationPhase.PARSE_CONFIGURATION;
+ }
+ }
+
+}