Skip to content

Configuration class's superclass may be incorrectly skipped if first importer isn't skipped till register bean phase of condition evaluation [SPR-16217] #20765

Closed as not planned
@spring-projects-issues

Description

@spring-projects-issues

Andy Wilkinson opened SPR-16217 and commented

ConfigurationClassParser populates its knownSuperclasses map during the configuration phase of condition evaluation. This means that a subclass that has a register bean phase condition can cause the superclass to be skipped even if another superclass that's encountered later would have included it.

The above is perhaps best illustrated by some tests:

import org.junit.Test;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.context.annotation.Import;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class KnownSuperclassesBug {

	@Test
	public void baseConfigurationIsIncludedWhenFirstSuperclassReferenceIsSkippedInRegisterBeanPhase() {
		try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
				RegisterBeanPhaseImportingConfiguration.class)) {
			context.getBean("someBean");
		}
	}

	@Test
	public void baseConfigurationIsIncludedWhenFirstSuperclassReferenceIsSkippedInParseConfigurationPhase() {
		try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
				ParseConfigurationPhaseImportingConfiguration.class)) {
			context.getBean("someBean");
		}
	}

	public static class RegisterBeanPhaseCondition implements ConfigurationCondition {

		@Override
		public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
			return false;
		}

		@Override
		public ConfigurationPhase getConfigurationPhase() {
			return ConfigurationPhase.REGISTER_BEAN;
		}

	}

	public static class ParseConfigurationPhaseCondition
			implements ConfigurationCondition {

		@Override
		public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
			return false;
		}

		@Override
		public ConfigurationPhase getConfigurationPhase() {
			return ConfigurationPhase.PARSE_CONFIGURATION;
		}

	}

	@Import({ RegisterBeanPhaseConditionConfiguration.class, BarConfiguration.class })
	public static class RegisterBeanPhaseImportingConfiguration {

	}

	@Import({ ParseConfigurationPhaseConditionConfiguration.class,
			BarConfiguration.class })
	public static class ParseConfigurationPhaseImportingConfiguration {

	}

	public static class BaseConfiguration {

		@Bean
		public String someBean() {
			return "foo";
		}

	}

	@Conditional(RegisterBeanPhaseCondition.class)
	public static class RegisterBeanPhaseConditionConfiguration
			extends BaseConfiguration {

	}

	@Conditional(ParseConfigurationPhaseCondition.class)
	public static class ParseConfigurationPhaseConditionConfiguration
			extends BaseConfiguration {

	}

	public static class BarConfiguration extends BaseConfiguration {

	}

}

My expectation is that both tests will pass. As things stand baseConfigurationIsIncludedWhenFirstSuperclassReferenceIsSkippedInRegisterBeanPhase fails and baseConfigurationIsIncludedWhenFirstSuperclassReferenceIsSkippedInParseConfigurationPhase() passes.

The first test fails because RegisterBeanPhaseConditionConfiguration is processed and BaseConfiguration is added to the knownSuperclasses map. When BarConfiguration is processed BaseConfiguration is already in the knownSuperclasses map so its "import" via BarConfiguration is lost. When the register bean phase condition on RegisterBeanPhaseConditionConfiguration is evaluated it doesn't match so it's skipped along with BaseConfiguration that it imports.


Affects: 4.3.12, 5.0.1

Reference URL: spring-projects/spring-boot#11063

Issue Links:

Referenced from: commits 47383fc, 08c95fb

1 votes, 5 watchers

Metadata

Metadata

Assignees

No one assigned

    Labels

    in: coreIssues in core modules (aop, beans, core, context, expression)status: supersededAn issue that has been superseded by anothertype: bugA general bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions