Replace DeferredBeanRegistrar with implicit ordering semantics

GenericApplicationContext-registered BeanRegistrars are invoked after other programmatic bean definitions. Configuration-imported BeanRegistrars participate in configuration class order and in particular in Boot's auto-configuration ordering.

Closes gh-21497
This commit is contained in:
Juergen Hoeller
2026-04-01 20:59:45 +02:00
parent 5e16e25109
commit 3bf31e45dd
10 changed files with 50 additions and 166 deletions
@@ -45,7 +45,6 @@ import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.BeanRegistryAdapter;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.DeferredBeanRegistrar;
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
@@ -425,14 +424,12 @@ class ConfigurationClassBeanDefinitionReader {
private void loadBeanDefinitionsFromBeanRegistrars(MultiValueMap<String, BeanRegistrar> registrars) {
registrars.values().forEach(registrarList -> registrarList.forEach(registrar -> {
if (!(registrar instanceof DeferredBeanRegistrar)) {
if (!(this.registry instanceof ListableBeanFactory beanFactory)) {
throw new IllegalStateException("Cannot support bean registrars since " +
this.registry.getClass().getName() + " does not implement ListableBeanFactory");
}
registrar.register(new BeanRegistryAdapter(
this.registry, beanFactory, this.environment, registrar.getClass()), this.environment);
if (!(this.registry instanceof ListableBeanFactory beanFactory)) {
throw new IllegalStateException("Cannot support bean registrars since " +
this.registry.getClass().getName() + " does not implement ListableBeanFactory");
}
registrar.register(new BeanRegistryAdapter(
this.registry, beanFactory, this.environment, registrar.getClass()), this.environment);
}));
}
@@ -94,13 +94,11 @@ import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationStartupAware;
import org.springframework.context.DeferredBeanRegistrar;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ConfigurationClassEnhancer.EnhancedConfiguration;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
@@ -210,7 +208,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1; // within PriorityOrdered, 1 before DeferredRegistryPostProcessor
return Ordered.LOWEST_PRECEDENCE; // within PriorityOrdered
}
/**
@@ -492,25 +490,6 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
while (!candidates.isEmpty());
// Process DeferredBeanRegistrars, if any.
List<DeferredBeanRegistrar> deferredBeanRegistrars = new ArrayList<>();
for (List<BeanRegistrar> registrars : this.beanRegistrars.values()) {
for (BeanRegistrar registrar : registrars) {
if (registrar instanceof DeferredBeanRegistrar deferredRegistrar) {
deferredBeanRegistrars.add(deferredRegistrar);
}
}
}
AnnotationAwareOrderComparator.sort(deferredBeanRegistrars);
for (DeferredBeanRegistrar registrar : deferredBeanRegistrars) {
if (!(registry instanceof ListableBeanFactory beanFactory)) {
throw new IllegalStateException("Cannot support bean registrars since " +
registry.getClass().getName() + " does not implement ListableBeanFactory");
}
registrar.register(new BeanRegistryAdapter(
registry, beanFactory, this.environment, registrar.getClass()), this.environment);
}
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (singletonRegistry != null && !singletonRegistry.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
singletonRegistry.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
@@ -23,7 +23,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.context.DeferredBeanRegistrar;
/**
* Indicates one or more <em>component classes</em> to import &mdash; typically
@@ -32,9 +31,9 @@ import org.springframework.context.DeferredBeanRegistrar;
* <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
*
* <p>Allows for importing {@code @Configuration} classes, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, and {@link BeanRegistrar}/{@link DeferredBeanRegistrar}
* implementations, as well as regular component classes
* (analogous to {@link AnnotationConfigApplicationContext#register}).
* {@link ImportBeanDefinitionRegistrar}, and {@link BeanRegistrar} implementations,
* as well as regular component classes (analogous to
* {@link AnnotationConfigApplicationContext#register}).
*
* <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
* accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
@@ -72,8 +71,7 @@ public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar},
* {@link BeanRegistrar}/{@link DeferredBeanRegistrar},
* {@link ImportBeanDefinitionRegistrar}, {@link BeanRegistrar},
* or regular component classes to import.
*/
Class<?>[] value();
@@ -44,10 +44,8 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.DeferredBeanRegistrar;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.ProtocolResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
@@ -612,18 +610,13 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
*/
public void register(BeanRegistrar... registrars) {
for (BeanRegistrar registrar : registrars) {
if (registrar instanceof DeferredBeanRegistrar deferredRegistrar) {
DeferredRegistryPostProcessor pp = (DeferredRegistryPostProcessor)
this.beanFactory.getSingleton(DEFERRED_REGISTRY_POST_PROCESSOR_BEAN_NAME);
if (pp == null) {
pp = new DeferredRegistryPostProcessor();
this.beanFactory.registerSingleton(DEFERRED_REGISTRY_POST_PROCESSOR_BEAN_NAME, pp);
}
pp.addRegistrar(deferredRegistrar);
}
else {
new BeanRegistryAdapter(this.beanFactory, getEnvironment(), registrar.getClass()).register(registrar);
DeferredRegistryPostProcessor pp = (DeferredRegistryPostProcessor)
this.beanFactory.getSingleton(DEFERRED_REGISTRY_POST_PROCESSOR_BEAN_NAME);
if (pp == null) {
pp = new DeferredRegistryPostProcessor();
this.beanFactory.registerSingleton(DEFERRED_REGISTRY_POST_PROCESSOR_BEAN_NAME, pp);
}
pp.addRegistrar(registrar);
}
}
@@ -671,25 +664,24 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
/**
* Internal post-processor for invoking DeferredBeanRegistrars at the end
* of the BeanDefinitionRegistryPostProcessor PriorityOrdered phase,
* right after a potential ConfigurationClassPostProcessor.
* right before a potential ConfigurationClassPostProcessor.
*/
private class DeferredRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
private final List<DeferredBeanRegistrar> registrars = new ArrayList<>();
private final List<BeanRegistrar> registrars = new ArrayList<>();
public void addRegistrar(DeferredBeanRegistrar registrar) {
public void addRegistrar(BeanRegistrar registrar) {
this.registrars.add(registrar);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; // within PriorityOrdered, 1 behind ConfigurationClassPostProcessor
return Ordered.LOWEST_PRECEDENCE - 1; // within PriorityOrdered, 1 before ConfigurationClassPostProcessor
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
AnnotationAwareOrderComparator.sort(this.registrars);
for (DeferredBeanRegistrar registrar : this.registrars) {
for (BeanRegistrar registrar : this.registrars) {
new BeanRegistryAdapter(beanFactory, getEnvironment(), registrar.getClass()).register(registrar);
}
}
@@ -25,6 +25,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.testfixture.beans.factory.BarRegistrar;
import org.springframework.context.testfixture.beans.factory.ConditionalBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.FooRegistrar;
import org.springframework.context.testfixture.beans.factory.GenericBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.ImportAwareBeanRegistrar;
@@ -33,11 +34,10 @@ import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Foo;
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Init;
import org.springframework.context.testfixture.context.annotation.registrar.BeanRegistrarConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.ConditionalBeanRegistrarConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.GenericBeanRegistrarConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.ImportAwareBeanRegistrarConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.MultipleBeanRegistrarsConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.MyDeferredBeanRegistrarConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.MyRegularBeanRegistrarConfiguration;
import org.springframework.context.testfixture.context.annotation.registrar.TestBeanConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
@@ -110,40 +110,41 @@ class BeanRegistrarConfigurationTests {
}
@Test
void regularBeanRegistrarWithConditionMet() {
void programmaticBeanRegistrarWithConditionNotMet() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(TestBeanConfiguration.class);
context.register(MyRegularBeanRegistrarConfiguration.class);
context.refresh();
assertThat(context.containsBean("myTestBean")).isTrue();
assertThat(context.getBean("myTestBean")).isInstanceOf(TestBean.class);
}
@Test
void regularBeanRegistrarWithConditionNotMet() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MyRegularBeanRegistrarConfiguration.class);
context.register(new ConditionalBeanRegistrar());
context.register(TestBeanConfiguration.class);
context.refresh();
assertThat(context.containsBean("myTestBean")).isFalse();
}
@Test
void deferredBeanRegistrarWithConditionMet() {
void programmaticBeanRegistrarWithConditionMet() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MyDeferredBeanRegistrarConfiguration.class);
context.register(TestBeanConfiguration.class);
context.register(new ConditionalBeanRegistrar());
context.registerBean("testBean", TestBean.class);
context.refresh();
assertThat(context.containsBean("myTestBean")).isTrue();
assertThat(context.getBean("myTestBean")).isInstanceOf(TestBean.class);
}
@Test
void deferredBeanRegistrarWithConditionNotMet() {
void importedBeanRegistrarWithConditionNotMet() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MyDeferredBeanRegistrarConfiguration.class);
context.register(ConditionalBeanRegistrarConfiguration.class);
context.register(TestBeanConfiguration.class);
context.refresh();
assertThat(context.containsBean("myTestBean")).isFalse();
}
@Test
void importedBeanRegistrarWithConditionMet() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(TestBeanConfiguration.class);
context.register(ConditionalBeanRegistrarConfiguration.class);
context.refresh();
assertThat(context.containsBean("myTestBean")).isTrue();
assertThat(context.getBean("myTestBean")).isInstanceOf(TestBean.class);
}
}
@@ -47,9 +47,8 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.testfixture.beans.factory.ConditionalBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.ImportAwareBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.MyDeferredBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.MyRegularBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.env.ConfigurableEnvironment;
@@ -649,42 +648,23 @@ class GenericApplicationContextTests {
}
@Test
void regularBeanRegistrarWithConditionMet() {
void beanRegistrarWithConditionNotMet() {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("testBean", TestBean.class);
context.register(new MyRegularBeanRegistrar());
context.refresh();
assertThat(context.containsBean("myTestBean")).isTrue();
assertThat(context.getBean("myTestBean")).isInstanceOf(TestBean.class);
}
@Test
void regularBeanRegistrarWithConditionNotMet() {
GenericApplicationContext context = new GenericApplicationContext();
context.register(new MyRegularBeanRegistrar());
context.registerBean("testBean", TestBean.class);
context.register(new ConditionalBeanRegistrar());
context.refresh();
assertThat(context.containsBean("myTestBean")).isFalse();
}
@Test
void deferredBeanRegistrarWithConditionMet() {
void beanRegistrarWithConditionMet() {
GenericApplicationContext context = new GenericApplicationContext();
context.register(new MyDeferredBeanRegistrar());
context.register(new ConditionalBeanRegistrar());
context.registerBean("testBean", TestBean.class);
context.refresh();
assertThat(context.containsBean("myTestBean")).isTrue();
assertThat(context.getBean("myTestBean")).isInstanceOf(TestBean.class);
}
@Test
void deferredBeanRegistrarWithConditionNotMet() {
GenericApplicationContext context = new GenericApplicationContext();
context.register(new MyDeferredBeanRegistrar());
context.refresh();
assertThat(context.containsBean("myTestBean")).isFalse();
}
private MergedBeanDefinitionPostProcessor registerMockMergedBeanDefinitionPostProcessor(GenericApplicationContext context) {
MergedBeanDefinitionPostProcessor bpp = mock();
@@ -22,7 +22,7 @@ import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.env.Environment;
public class MyRegularBeanRegistrar implements BeanRegistrar {
public class ConditionalBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
@@ -1,37 +0,0 @@
/*
* Copyright 2002-present 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
*
* https://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.testfixture.beans.factory;
import org.springframework.beans.factory.BeanRegistry;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.DeferredBeanRegistrar;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.env.Environment;
public class MyDeferredBeanRegistrar implements DeferredBeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
if (registry.containsBean("testBean") &&
registry.containsBean(TestBean.class) &&
registry.containsBean(new ParameterizedTypeReference<Comparable<Object>>() {
})) {
registry.registerBean("myTestBean", TestBean.class);
}
}
}
@@ -18,9 +18,9 @@ package org.springframework.context.testfixture.context.annotation.registrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.testfixture.beans.factory.MyDeferredBeanRegistrar;
import org.springframework.context.testfixture.beans.factory.ConditionalBeanRegistrar;
@Configuration
@Import(MyDeferredBeanRegistrar.class)
public class MyDeferredBeanRegistrarConfiguration {
@Import(ConditionalBeanRegistrar.class)
public class ConditionalBeanRegistrarConfiguration {
}
@@ -1,26 +0,0 @@
/*
* Copyright 2002-present 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
*
* https://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.testfixture.context.annotation.registrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.testfixture.beans.factory.MyRegularBeanRegistrar;
@Configuration
@Import(MyRegularBeanRegistrar.class)
public class MyRegularBeanRegistrarConfiguration {
}