Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A configuration class superclass that is skipped due to register bean phase conditions is ignored when another configuration class that extends it is processed #28676

Closed
wilkinsona opened this issue Jun 22, 2022 · 0 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug
Milestone

Comments

@wilkinsona
Copy link
Member

wilkinsona commented Jun 22, 2022

Affects: 6.0.x. I expect 5.3.x to also be affected.

I think @mbhave and I have found a bug in configuration class processing related to condition evaluation. It's hopefully illustrated by the following tests:

import org.junit.jupiter.api.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.Configuration;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.context.annotation.Import;
import org.springframework.core.type.AnnotatedTypeMetadata;

import static org.assertj.core.api.Assertions.assertThat;

class ConfigurationPhasesKnownSuperclassesTests {

	@Test
	void superclassSkippedInParseConfigurationPhaseShouldNotPreventSubsequentProcessingOfSameSuperclass() {
		try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
				ParseConfigurationPhase.class)) {
			assertThat(context.getBean("subclassBean")).isEqualTo("bravo");
			assertThat(context.getBean("superclassBean")).isEqualTo("superclass");
		}
	}

	@Test
	void superclassSkippedInRegisterBeanPhaseShouldNotPreventSubsequentProcessingOfSameSuperclass() {
		try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
				RegisterBeanPhase.class)) {
			assertThat(context.getBean("subclassBean")).isEqualTo("bravo");
			assertThat(context.getBean("superclassBean")).isEqualTo("superclass");
		}
	}

	@Configuration(proxyBeanMethods = false)
	static class Example {

		@Bean
		String superclassBean() {
			return "superclass";
		}

	}

	@Configuration(proxyBeanMethods = false)
	@Import({ RegisterBeanPhaseExample.class, BravoExample.class })
	static class RegisterBeanPhase {

	}

	@Conditional(NonMatchingRegisterBeanPhaseCondition.class)
	@Configuration(proxyBeanMethods = false)
	static class RegisterBeanPhaseExample extends Example {

		@Bean
		String subclassBean() {
			return "alpha";
		}

	}

	@Configuration(proxyBeanMethods = false)
	@Import({ ParseConfigurationPhaseExample.class, BravoExample.class })
	static class ParseConfigurationPhase {

	}

	@Conditional(NonMatchingParseConfigurationPhaseCondition.class)
	@Configuration(proxyBeanMethods = false)
	static class ParseConfigurationPhaseExample extends Example {

		@Bean
		String subclassBean() {
			return "alpha";
		}

	}

	@Configuration(proxyBeanMethods = false)
	static class BravoExample extends Example {

		@Bean
		String subclassBean() {
			return "bravo";
		}

	}

	static class NonMatchingRegisterBeanPhaseCondition implements ConfigurationCondition {

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

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

	}

	static class NonMatchingParseConfigurationPhaseCondition implements ConfigurationCondition {

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

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

	}

}

superclassSkippedInParseConfigurationPhaseShouldNotPreventSubsequentProcessingOfSameSuperclass passes but superclassSkippedInRegisterBeanPhaseShouldNotPreventSubsequentProcessingOfSameSuperclass fails.

The register bean phase test fails due to the knownSuperclasses map in ConfigurationClassParser being polluted. Due to the parse configuration phase conditions matching an entry is added to the map for Example -> RegisterBeanPhaseExample. Subsequently, the register bean phase condition on RegisterBeanPhaseExample does not match, so neither it nor Example are processed. When BravoExample is then parsed, ConfigurationClassParser considers its superclass Example. It's skipped due to the existing entry in the knownSuperclasses map, despite the fact that Example was never actually processed due to the conditions on RegisterBeanPhaseExample. The end result is that the context is left without a bean named superclassBean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug
Projects
None yet
Development

No branches or pull requests

4 participants