diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistrar.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistrar.java
new file mode 100644
index 0000000000..cc05c62c80
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistrar.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2025 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.beans.factory;
+
+import org.springframework.core.env.Environment;
+
+/**
+ * Contract for registering beans programmatically.
+ *
+ *
Typically imported with an {@link org.springframework.context.annotation.Import @Import}
+ * annotation on {@link org.springframework.context.annotation.Configuration @Configuration}
+ * classes.
+ *
+ * @Configuration
+ * @Import(MyBeanRegistrar.class)
+ * class MyConfiguration {
+ * }
+ *
+ * The bean registrar implementation uses {@link BeanRegistry} and {@link Environment}
+ * APIs to register beans programmatically in a concise and flexible way.
+ *
+ * class MyBeanRegistrar implements BeanRegistrar {
+ *
+ * @Override
+ * public void register(BeanRegistry registry, Environment env) {
+ * registry.registerBean("foo", Foo.class);
+ * registry.registerBean("bar", Bar.class, spec -> spec
+ * .prototype()
+ * .lazyInit()
+ * .description("Custom description")
+ * .supplier(context -> new Bar(context.bean(Foo.class))));
+ * if (env.matchesProfiles("baz")) {
+ * registry.registerBean(Baz.class, spec -> spec
+ * .supplier(context -> new Baz("Hello World!")));
+ * }
+ * }
+ * }
+ *
+ * In Kotlin, it is recommended to use {@code BeanRegistrarDsl} instead of
+ * implementing {@code BeanRegistrar}.
+ *
+ * @author Sebastien Deleuze
+ * @since 7.0
+ */
+@FunctionalInterface
+public interface BeanRegistrar {
+
+ /**
+ * Register beans in a programmatic way.
+ * @param registry the bean registry
+ * @param env the environment that can be used to get the active profile or some properties
+ */
+ void register(BeanRegistry registry, Environment env);
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistry.java
new file mode 100644
index 0000000000..26cbc577ac
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistry.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2002-2025 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.beans.factory;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.core.ResolvableType;
+import org.springframework.core.env.Environment;
+
+/**
+ * Used in {@link BeanRegistrar#register(BeanRegistry, Environment)} to expose
+ * programmatic bean registration capabilities.
+ *
+ * @author Sebastien Deleuze
+ * @since 7.0
+ */
+public interface BeanRegistry {
+
+ /**
+ * Register a bean from the given bean class, which will be instantiated
+ * using the related {@link BeanUtils#getResolvableConstructor resolvable constructor}
+ * if any.
+ * @param beanClass the class of the bean
+ * @return the generated bean name
+ */
+ String registerBean(Class beanClass);
+
+ /**
+ * Register a bean from the given bean class, customizing it with the customizer
+ * callback. The bean will be instantiated using the supplier that can be
+ * configured in the customizer callback, or will be tentatively instantiated
+ * with its {@link BeanUtils#getResolvableConstructor resolvable constructor}
+ * otherwise.
+ * @param beanClass the class of the bean
+ * @param customizer callback to customize other bean properties than the name
+ * @return the generated bean name
+ */
+ String registerBean(Class beanClass, Consumer> customizer);
+
+ /**
+ * Register a bean from the given bean class, which will be instantiated
+ * using the related {@link BeanUtils#getResolvableConstructor resolvable constructor}
+ * if any.
+ * @param name the name of the bean
+ * @param beanClass the class of the bean
+ */
+ void registerBean(String name, Class beanClass);
+
+ /**
+ * Register a bean from the given bean class, customizing it with the customizer
+ * callback. The bean will be instantiated using the supplier that can be
+ * configured in the customizer callback, or will be tentatively instantiated with its
+ * {@link BeanUtils#getResolvableConstructor resolvable constructor} otherwise.
+ * @param name the name of the bean
+ * @param beanClass the class of the bean
+ * @param customizer callback to customize other bean properties than the name
+ */
+ void registerBean(String name, Class beanClass, Consumer> customizer);
+
+ /**
+ * Specification for customizing a bean.
+ * @param the bean type
+ */
+ interface Spec {
+
+ /**
+ * Allow for instantiating this bean on a background thread.
+ * @see AbstractBeanDefinition#setBackgroundInit(boolean)
+ */
+ Spec backgroundInit();
+
+ /**
+ * Set a human-readable description of this bean.
+ * @see BeanDefinition#setDescription(String)
+ */
+ Spec description(String description);
+
+ /**
+ * Configure this bean as a fallback autowire candidate.
+ * @see BeanDefinition#setFallback(boolean)
+ * @see #primary
+ */
+ Spec fallback();
+
+ /**
+ * Hint that this bean has an infrastructure role, meaning it has no
+ * relevance to the end-user.
+ * @see BeanDefinition#setRole(int)
+ * @see BeanDefinition#ROLE_INFRASTRUCTURE
+ */
+ Spec infrastructure();
+
+ /**
+ * Configure this bean as lazily initialized.
+ * @see BeanDefinition#setLazyInit(boolean)
+ */
+ Spec lazyInit();
+
+ /**
+ * Configure this bean as not a candidate for getting autowired into some
+ * other bean.
+ * @see BeanDefinition#setAutowireCandidate(boolean)
+ */
+ Spec notAutowirable();
+
+ /**
+ * The sort order of this bean. This is analogous to the
+ * {@code @Order} annotation.
+ * @see AbstractBeanDefinition#ORDER_ATTRIBUTE
+ */
+ Spec order(int order);
+
+ /**
+ * Configure this bean as a primary autowire candidate.
+ * @see BeanDefinition#setPrimary(boolean)
+ * @see #fallback
+ */
+ Spec primary();
+
+ /**
+ * Configure this bean with a prototype scope.
+ * @see BeanDefinition#setScope(String)
+ * @see BeanDefinition#SCOPE_PROTOTYPE
+ */
+ Spec prototype();
+
+ /**
+ * Set the supplier to construct a bean instance.
+ * @see AbstractBeanDefinition#setInstanceSupplier(Supplier)
+ */
+ Spec supplier(Function supplier);
+ }
+
+ /**
+ * Context available from the bean instance supplier designed to give access
+ * to bean dependencies.
+ */
+ interface SupplierContext {
+
+ /**
+ * Return the bean instance that uniquely matches the given object type,
+ * if any.
+ * @param requiredType type the bean must match; can be an interface or
+ * superclass
+ * @return an instance of the single bean matching the required type
+ * @see BeanFactory#getBean(String)
+ */
+ T bean(Class requiredType) throws BeansException;
+
+ /**
+ * Return an instance, which may be shared or independent, of the
+ * specified bean.
+ * @param name the name of the bean to retrieve
+ * @param requiredType type the bean must match; can be an interface or superclass
+ * @return an instance of the bean.
+ * @see BeanFactory#getBean(String, Class)
+ */
+ T bean(String name, Class requiredType) throws BeansException;
+
+ /**
+ * Return a provider for the specified bean, allowing for lazy on-demand retrieval
+ * of instances, including availability and uniqueness options.
+ * For matching a generic type, consider {@link #beanProvider(ResolvableType)}.
+ * @param requiredType type the bean must match; can be an interface or superclass
+ * @return a corresponding provider handle
+ * @see BeanFactory#getBeanProvider(Class)
+ */
+ ObjectProvider beanProvider(Class requiredType);
+
+ /**
+ * Return a provider for the specified bean, allowing for lazy on-demand retrieval
+ * of instances, including availability and uniqueness options. This variant allows
+ * for specifying a generic type to match, similar to reflective injection points
+ * with generic type declarations in method/constructor parameters.
+ * Note that collections of beans are not supported here, in contrast to reflective
+ * injection points. For programmatically retrieving a list of beans matching a
+ * specific type, specify the actual bean type as an argument here and subsequently
+ * use {@link ObjectProvider#orderedStream()} or its lazy streaming/iteration options.
+ *
Also, generics matching is strict here, as per the Java assignment rules.
+ * For lenient fallback matching with unchecked semantics (similar to the 'unchecked'
+ * Java compiler warning), consider calling {@link #beanProvider(Class)} with the
+ * raw type as a second step if no full generic match is
+ * {@link ObjectProvider#getIfAvailable() available} with this variant.
+ * @param requiredType type the bean must match; can be a generic type declaration
+ * @return a corresponding provider handle
+ * @see BeanFactory#getBeanProvider(ResolvableType)
+ */
+ ObjectProvider beanProvider(ResolvableType requiredType);
+ }
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanRegistryAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanRegistryAdapter.java
new file mode 100644
index 0000000000..dfc0c0aac9
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanRegistryAdapter.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2002-2025 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.beans.factory.support;
+
+import java.lang.reflect.Constructor;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanRegistrar;
+import org.springframework.beans.factory.BeanRegistry;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
+import org.springframework.core.ResolvableType;
+import org.springframework.util.MultiValueMap;
+
+/**
+ * {@link BeanRegistry} implementation that delegates to
+ * {@link BeanDefinitionRegistry} and {@link ListableBeanFactory}.
+ *
+ * @author Sebastien Deleuze
+ * @since 7.0
+ */
+public class BeanRegistryAdapter implements BeanRegistry {
+
+ private final BeanDefinitionRegistry beanRegistry;
+
+ private final ListableBeanFactory beanFactory;
+
+ private final Class extends BeanRegistrar> beanRegistrarClass;
+
+ private final @Nullable MultiValueMap customizers;
+
+
+ public BeanRegistryAdapter(BeanDefinitionRegistry beanRegistry, ListableBeanFactory beanFactory,
+ Class extends BeanRegistrar> beanRegistrarClass) {
+ this(beanRegistry, beanFactory, beanRegistrarClass, null);
+ }
+
+ public BeanRegistryAdapter(BeanDefinitionRegistry beanRegistry, ListableBeanFactory beanFactory,
+ Class extends BeanRegistrar> beanRegistrarClass, @Nullable MultiValueMap customizers) {
+
+ this.beanRegistry = beanRegistry;
+ this.beanFactory = beanFactory;
+ this.beanRegistrarClass = beanRegistrarClass;
+ this.customizers = customizers;
+ }
+
+ @Override
+ public String registerBean(Class beanClass) {
+ String beanName = BeanDefinitionReaderUtils.uniqueBeanName(beanClass.getName(), this.beanRegistry);
+ registerBean(beanName, beanClass);
+ return beanName;
+ }
+
+ @Override
+ public String registerBean(Class beanClass, Consumer> customizer) {
+ String beanName = BeanDefinitionReaderUtils.uniqueBeanName(beanClass.getName(), this.beanRegistry);
+ registerBean(beanName, beanClass, customizer);
+ return beanName;
+ }
+
+ @Override
+ public void registerBean(String name, Class beanClass) {
+ BeanRegistrarBeanDefinition beanDefinition = new BeanRegistrarBeanDefinition(beanClass, this.beanRegistrarClass);
+ if (this.customizers != null && this.customizers.containsKey(name)) {
+ for (BeanDefinitionCustomizer customizer : this.customizers.get(name)) {
+ customizer.customize(beanDefinition);
+ }
+ }
+ this.beanRegistry.registerBeanDefinition(name, beanDefinition);
+ }
+
+ @Override
+ public void registerBean(String name, Class beanClass, Consumer> spec) {
+ BeanRegistrarBeanDefinition beanDefinition = new BeanRegistrarBeanDefinition(beanClass, this.beanRegistrarClass);
+ spec.accept(new BeanSpecAdapter<>(beanDefinition, this.beanFactory));
+ if (this.customizers != null && this.customizers.containsKey(name)) {
+ for (BeanDefinitionCustomizer customizer : this.customizers.get(name)) {
+ customizer.customize(beanDefinition);
+ }
+ }
+ this.beanRegistry.registerBeanDefinition(name, beanDefinition);
+ }
+
+
+ /**
+ * {@link RootBeanDefinition} subclass for {@code #registerBean} based
+ * registrations with constructors resolution match{@link BeanUtils#getResolvableConstructor}
+ * behavior. It also sets the bean registrar class as the source.
+ */
+ @SuppressWarnings("serial")
+ private static class BeanRegistrarBeanDefinition extends RootBeanDefinition {
+
+ public BeanRegistrarBeanDefinition(Class> beanClass, Class extends BeanRegistrar> beanRegistrarClass) {
+ super(beanClass);
+ this.setSource(beanRegistrarClass);
+ this.setAttribute("aotProcessingIgnoreRegistration", true);
+ }
+
+ public BeanRegistrarBeanDefinition(BeanRegistrarBeanDefinition original) {
+ super(original);
+ }
+
+ @Override
+ public Constructor> @Nullable [] getPreferredConstructors() {
+ if (this.getInstanceSupplier() != null) {
+ return null;
+ }
+ try {
+ return new Constructor>[] { BeanUtils.getResolvableConstructor(getBeanClass()) };
+ }
+ catch (IllegalStateException ex) {
+ return null;
+ }
+ }
+
+ @Override
+ public RootBeanDefinition cloneBeanDefinition() {
+ return new BeanRegistrarBeanDefinition(this);
+ }
+ }
+
+ static class BeanSpecAdapter implements Spec {
+
+ private final RootBeanDefinition beanDefinition;
+
+ private final ListableBeanFactory beanFactory;
+
+ public BeanSpecAdapter(RootBeanDefinition beanDefinition, ListableBeanFactory beanFactory) {
+ this.beanDefinition = beanDefinition;
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public Spec backgroundInit() {
+ this.beanDefinition.setBackgroundInit(true);
+ return this;
+ }
+
+ @Override
+ public Spec fallback() {
+ this.beanDefinition.setFallback(true);
+ return this;
+ }
+
+ @Override
+ public Spec primary() {
+ this.beanDefinition.setPrimary(true);
+ return this;
+ }
+
+ @Override
+ public Spec description(String description) {
+ this.beanDefinition.setDescription(description);
+ return this;
+ }
+
+ @Override
+ public Spec infrastructure() {
+ this.beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ return this;
+ }
+
+ @Override
+ public Spec lazyInit() {
+ this.beanDefinition.setLazyInit(true);
+ return this;
+ }
+
+ @Override
+ public Spec notAutowirable() {
+ this.beanDefinition.setAutowireCandidate(false);
+ return this;
+ }
+
+ @Override
+ public Spec order(int order) {
+ this.beanDefinition.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, order);
+ return this;
+ }
+
+ @Override
+ public Spec prototype() {
+ this.beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
+ return this;
+ }
+
+ @Override
+ public Spec supplier(Function supplier) {
+ this.beanDefinition.setInstanceSupplier(() ->
+ supplier.apply(new SupplierContextAdapter(this.beanFactory)));
+ return this;
+ }
+ }
+
+ static class SupplierContextAdapter implements SupplierContext {
+
+ private final ListableBeanFactory beanFactory;
+
+ public SupplierContextAdapter(ListableBeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public T bean(Class requiredType) throws BeansException {
+ return this.beanFactory.getBean(requiredType);
+ }
+
+ @Override
+ public T bean(String name, Class requiredType) throws BeansException {
+ return this.beanFactory.getBean(name, requiredType);
+ }
+
+ @Override
+ public ObjectProvider beanProvider(Class requiredType) {
+ return this.beanFactory.getBeanProvider(requiredType);
+ }
+
+ @Override
+ public ObjectProvider beanProvider(ResolvableType requiredType) {
+ return this.beanFactory.getBeanProvider(requiredType);
+ }
+ }
+
+}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanRegistryAdapterTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanRegistryAdapterTests.java
new file mode 100644
index 0000000000..81e0c3840e
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanRegistryAdapterTests.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2002-2025 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.beans.factory.support;
+
+import java.util.function.Supplier;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.BeanRegistrar;
+import org.springframework.beans.factory.BeanRegistry;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.StandardEnvironment;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link BeanRegistryAdapter}.
+ *
+ * @author Sebastien Deleuze
+ */
+public class BeanRegistryAdapterTests {
+
+ private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
+
+ private final Environment env = new StandardEnvironment();
+
+ @Test
+ void defaultBackgroundInit() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
+ new DefaultBeanRegistrar().register(adapter, env);
+ AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.isBackgroundInit()).isFalse();
+ }
+
+ @Test
+ void enableBackgroundInit() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, BackgroundInitBeanRegistrar.class);
+ new BackgroundInitBeanRegistrar().register(adapter, env);
+ AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.isBackgroundInit()).isTrue();
+ }
+
+ @Test
+ void defaultDescription() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
+ new DefaultBeanRegistrar().register(adapter, env);
+ BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.getDescription()).isNull();
+ }
+
+ @Test
+ void customDescription() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, CustomDescriptionBeanRegistrar.class);
+ new CustomDescriptionBeanRegistrar().register(adapter, env);
+ BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.getDescription()).isEqualTo("custom");
+ }
+
+ @Test
+ void defaultFallback() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
+ new DefaultBeanRegistrar().register(adapter, env);
+ BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.isFallback()).isFalse();
+ }
+
+ @Test
+ void enableFallback() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, FallbackBeanRegistrar.class);
+ new FallbackBeanRegistrar().register(adapter, env);
+ BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.isFallback()).isTrue();
+ }
+
+ @Test
+ void defaultRole() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
+ new DefaultBeanRegistrar().register(adapter, env);
+ BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.getRole()).isEqualTo(AbstractBeanDefinition.ROLE_APPLICATION);
+ }
+
+ @Test
+ void infrastructureRole() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, InfrastructureBeanRegistrar.class);
+ new InfrastructureBeanRegistrar().register(adapter, env);
+ BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.getRole()).isEqualTo(AbstractBeanDefinition.ROLE_INFRASTRUCTURE);
+ }
+
+ @Test
+ void defaultLazyInit() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
+ new DefaultBeanRegistrar().register(adapter, env);
+ AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.isLazyInit()).isFalse();
+ }
+
+ @Test
+ void enableLazyInit() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, LazyInitBeanRegistrar.class);
+ new LazyInitBeanRegistrar().register(adapter, env);
+ AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.isLazyInit()).isTrue();
+ }
+
+ @Test
+ void defaultAutowirable() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
+ new DefaultBeanRegistrar().register(adapter, env);
+ AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.isAutowireCandidate()).isTrue();
+ }
+
+ @Test
+ void notAutowirable() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, NotAutowirableBeanRegistrar.class);
+ new NotAutowirableBeanRegistrar().register(adapter, env);
+ AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.isAutowireCandidate()).isFalse();
+ }
+
+ @Test
+ void defaultOrder() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
+ new DefaultBeanRegistrar().register(adapter, env);
+ AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
+ Integer order = (Integer)beanDefinition.getAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE);
+ assertThat(order).isNull();
+ }
+
+ @Test
+ void customOrder() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, CustomOrderBeanRegistrar.class);
+ new CustomOrderBeanRegistrar().register(adapter, env);
+ AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
+ Integer order = (Integer)beanDefinition.getAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE);
+ assertThat(order).isEqualTo(1);
+ }
+
+ @Test
+ void defaultPrimary() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
+ new DefaultBeanRegistrar().register(adapter, env);
+ BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.isPrimary()).isFalse();
+ }
+
+ @Test
+ void enablePrimary() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, PrimaryBeanRegistrar.class);
+ new PrimaryBeanRegistrar().register(adapter, env);
+ BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.isPrimary()).isTrue();
+ }
+
+ @Test
+ void defaultScope() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
+ new DefaultBeanRegistrar().register(adapter, env);
+ BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.getScope()).isEqualTo(AbstractBeanDefinition.SCOPE_DEFAULT);
+ }
+
+ @Test
+ void prototypeScope() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, PrototypeBeanRegistrar.class);
+ new PrototypeBeanRegistrar().register(adapter, env);
+ BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.getScope()).isEqualTo(AbstractBeanDefinition.SCOPE_PROTOTYPE);
+ }
+
+ @Test
+ void defaultSupplier() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
+ new DefaultBeanRegistrar().register(adapter, env);
+ AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition)this.beanFactory.getBeanDefinition("foo");
+ assertThat(beanDefinition.getInstanceSupplier()).isNull();
+ }
+
+ @Test
+ void customSupplier() {
+ BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, SupplierBeanRegistrar.class);
+ new SupplierBeanRegistrar().register(adapter, env);
+ AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition)this.beanFactory.getBeanDefinition("foo");
+ Supplier> supplier = beanDefinition.getInstanceSupplier();
+ assertThat(supplier).isNotNull();
+ assertThat(supplier.get()).isNotNull().isInstanceOf(Foo.class);
+ }
+
+
+ private static class DefaultBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class);
+ }
+ }
+
+ private static class BackgroundInitBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class, BeanRegistry.Spec::backgroundInit);
+ }
+ }
+
+ private static class CustomDescriptionBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class, spec -> spec.description("custom"));
+ }
+ }
+
+ private static class FallbackBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class, BeanRegistry.Spec::fallback);
+ }
+ }
+
+ private static class InfrastructureBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class, BeanRegistry.Spec::infrastructure);
+ }
+ }
+
+ private static class LazyInitBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class, BeanRegistry.Spec::lazyInit);
+ }
+ }
+
+ private static class NotAutowirableBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class, BeanRegistry.Spec::notAutowirable);
+ }
+ }
+
+ private static class CustomOrderBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class, spec -> spec.order(1));
+ }
+ }
+
+ private static class PrimaryBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class, BeanRegistry.Spec::primary);
+ }
+ }
+
+ private static class PrototypeBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class, BeanRegistry.Spec::prototype);
+ }
+ }
+
+ private static class SupplierBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class, spec -> spec.supplier(context -> new Foo()));
+ }
+ }
+
+ private static class Foo {}
+
+}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
index f9258139e1..da4e23a2c2 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
@@ -24,6 +24,7 @@ import java.util.Set;
import org.jspecify.annotations.Nullable;
+import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
@@ -65,6 +66,8 @@ final class ConfigurationClass {
private final Map> importedResources =
new LinkedHashMap<>();
+ private final Set beanRegistrars = new LinkedHashSet<>();
+
private final Map importBeanDefinitionRegistrars =
new LinkedHashMap<>();
@@ -219,6 +222,14 @@ final class ConfigurationClass {
return this.importedResources;
}
+ void addBeanRegistrar(BeanRegistrar beanRegistrar) {
+ this.beanRegistrars.add(beanRegistrar);
+ }
+
+ public Set getBeanRegistrars() {
+ return this.beanRegistrars;
+ }
+
void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
}
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 65f8bc5150..1688542e13 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -29,6 +29,8 @@ import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.beans.factory.BeanRegistrar;
+import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -41,6 +43,7 @@ import org.springframework.beans.factory.support.BeanDefinitionOverrideException
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
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.annotation.ConfigurationCondition.ConfigurationPhase;
@@ -146,7 +149,8 @@ class ConfigurationClassBeanDefinitionReader {
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
- loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
+ loadBeanDefinitionsFromImportBeanDefinitionRegistrars(configClass.getImportBeanDefinitionRegistrars());
+ loadBeanDefinitionsFromBeanRegistrars(configClass.getBeanRegistrars());
}
/**
@@ -395,11 +399,19 @@ class ConfigurationClassBeanDefinitionReader {
});
}
- private void loadBeanDefinitionsFromRegistrars(Map registrars) {
+ private void loadBeanDefinitionsFromImportBeanDefinitionRegistrars(Map registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}
+ private void loadBeanDefinitionsFromBeanRegistrars(Set registrars) {
+ Assert.isInstanceOf(ListableBeanFactory.class, this.registry,
+ "Cannot support bean registrars since " + this.registry.getClass().getName() +
+ " does not implement BeanDefinitionRegistry");
+ registrars.forEach(registrar -> registrar.register(new BeanRegistryAdapter(this.registry,
+ (ListableBeanFactory) this.registry, registrar.getClass()), this.environment));
+ }
+
/**
* {@link RootBeanDefinition} marker subclass used to signify that a bean definition
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 f34a326481..54250ee20a 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
@@ -40,6 +40,7 @@ import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
@@ -597,6 +598,13 @@ class ConfigurationClassParser {
processImports(configClass, currentSourceClass, importSourceClasses, filter, false);
}
}
+ else if (candidate.isAssignable(BeanRegistrar.class)) {
+ Class> candidateClass = candidate.loadClass();
+ BeanRegistrar registrar =
+ ParserStrategyUtils.instantiateClass(candidateClass, BeanRegistrar.class,
+ this.environment, this.resourceLoader, this.registry);
+ configClass.addBeanRegistrar(registrar);
+ }
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java
index cf39cb8038..57f6ee7d6a 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java
@@ -20,7 +20,9 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -40,8 +42,12 @@ import org.jspecify.annotations.Nullable;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aot.generate.GeneratedMethod;
+import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.generate.GenerationContext;
+import org.springframework.aot.generate.MethodReference;
+import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.ResourceHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
@@ -49,7 +55,10 @@ import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanRegistrar;
+import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
+import org.springframework.beans.factory.aot.AotServices;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
@@ -60,6 +69,7 @@ import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments;
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragmentsDecorator;
import org.springframework.beans.factory.aot.InstanceSupplierCodeGenerator;
import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -73,6 +83,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
+import org.springframework.beans.factory.support.BeanRegistryAdapter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
@@ -99,6 +110,7 @@ import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.javapoet.MethodSpec;
@@ -106,6 +118,10 @@ import org.springframework.javapoet.ParameterizedTypeName;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
/**
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
@@ -181,6 +197,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
@SuppressWarnings("NullAway.Init")
private List propertySourceDescriptors;
+ private Set beanRegistrars = new LinkedHashSet<>();
+
@Override
public int getOrder() {
@@ -323,7 +341,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
public @Nullable BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
boolean hasPropertySourceDescriptors = !CollectionUtils.isEmpty(this.propertySourceDescriptors);
boolean hasImportRegistry = beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME);
- if (hasPropertySourceDescriptors || hasImportRegistry) {
+ boolean hasBeanRegistrars = !this.beanRegistrars.isEmpty();
+ if (hasPropertySourceDescriptors || hasImportRegistry || hasBeanRegistrars) {
return (generationContext, code) -> {
if (hasPropertySourceDescriptors) {
new PropertySourcesAotContribution(this.propertySourceDescriptors, this::resolvePropertySourceLocation)
@@ -332,6 +351,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
if (hasImportRegistry) {
new ImportAwareAotContribution(beanFactory).applyTo(generationContext, code);
}
+ if (hasBeanRegistrars) {
+ new BeanRegistrarAotContribution(this.beanRegistrars, beanFactory).applyTo(generationContext, code);
+ }
};
}
return null;
@@ -420,6 +442,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
+ for (ConfigurationClass configClass : configClasses) {
+ this.beanRegistrars.addAll(configClass.getBeanRegistrars());
+ }
alreadyParsed.addAll(configClasses);
processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();
@@ -815,4 +840,182 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
}
+ private static class BeanRegistrarAotContribution implements BeanFactoryInitializationAotContribution {
+
+ private static final String CUSTOMIZER_MAP_VARIABLE = "customizers";
+
+ private static final String ENVIRONMENT_VARIABLE = "environment";
+
+ private final Set beanRegistrars;
+
+ private final ConfigurableListableBeanFactory beanFactory;
+
+ private final AotServices aotProcessors;
+
+ public BeanRegistrarAotContribution(Set beanRegistrars, ConfigurableListableBeanFactory beanFactory) {
+ this.beanRegistrars = beanRegistrars;
+ this.beanFactory = beanFactory;
+ this.aotProcessors = AotServices.factoriesAndBeans(this.beanFactory).load(BeanRegistrationAotProcessor.class);
+ }
+
+ @Override
+ public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
+ GeneratedMethod generatedMethod = beanFactoryInitializationCode.getMethods().add(
+ "applyBeanRegistrars", builder -> this.generateApplyBeanRegistrarsMethod(builder, generationContext));
+ beanFactoryInitializationCode.addInitializer(generatedMethod.toMethodReference());
+ }
+
+ private void generateApplyBeanRegistrarsMethod(MethodSpec.Builder method, GenerationContext generationContext) {
+ ReflectionHints reflectionHints = generationContext.getRuntimeHints().reflection();
+ method.addJavadoc("Apply bean registrars.");
+ method.addModifiers(Modifier.PRIVATE);
+ method.addParameter(ListableBeanFactory.class, BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE);
+ method.addParameter(Environment.class, ENVIRONMENT_VARIABLE);
+ method.addCode(generateCustomizerMap());
+
+ for (String name : this.beanFactory.getBeanDefinitionNames()) {
+ BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition(name);
+ if (beanDefinition.getSource() instanceof Class> sourceClass
+ && BeanRegistrar.class.isAssignableFrom(sourceClass)) {
+
+ for (BeanRegistrationAotProcessor aotProcessor : this.aotProcessors) {
+ BeanRegistrationAotContribution contribution =
+ aotProcessor.processAheadOfTime(RegisteredBean.of(this.beanFactory, name));
+ if (contribution != null) {
+ contribution.applyTo(generationContext,
+ new UnsupportedBeanRegistrationCode(name, aotProcessor.getClass()));
+ }
+ }
+ if (beanDefinition instanceof RootBeanDefinition rootBeanDefinition) {
+ if (rootBeanDefinition.getPreferredConstructors() != null) {
+ for (Constructor> constructor : rootBeanDefinition.getPreferredConstructors()) {
+ reflectionHints.registerConstructor(constructor, ExecutableMode.INVOKE);
+ }
+ }
+ if (!ObjectUtils.isEmpty(rootBeanDefinition.getInitMethodNames())) {
+ method.addCode(generateInitDestroyMethods(name, rootBeanDefinition,
+ rootBeanDefinition.getInitMethodNames(), "setInitMethodNames", reflectionHints));
+ }
+ if (!ObjectUtils.isEmpty(rootBeanDefinition.getDestroyMethodNames())) {
+ method.addCode(generateInitDestroyMethods(name, rootBeanDefinition,
+ rootBeanDefinition.getDestroyMethodNames(), "setDestroyMethodNames", reflectionHints));
+ }
+ checkUnsupportedFeatures(rootBeanDefinition);
+ }
+ }
+ }
+ method.addCode(generateRegisterCode());
+ }
+
+ private void checkUnsupportedFeatures(AbstractBeanDefinition beanDefinition) {
+ if (!ObjectUtils.isEmpty(beanDefinition.getFactoryBeanName())) {
+ throw new UnsupportedOperationException("AOT post processing of the factory bean name is not supported yet with BeanRegistrar");
+ }
+ if (beanDefinition.hasConstructorArgumentValues()) {
+ throw new UnsupportedOperationException("AOT post processing of argument values is not supported yet with BeanRegistrar");
+ }
+ if (!beanDefinition.getQualifiers().isEmpty()) {
+ throw new UnsupportedOperationException("AOT post processing of qualifiers is not supported yet with BeanRegistrar");
+ }
+ for (String attributeName : beanDefinition.attributeNames()) {
+ if (!attributeName.equals(AbstractBeanDefinition.ORDER_ATTRIBUTE)
+ && !attributeName.equals("aotProcessingIgnoreRegistration")) {
+ throw new UnsupportedOperationException("AOT post processing of attribute " + attributeName +
+ " is not supported yet with BeanRegistrar");
+ }
+ }
+ }
+
+ private CodeBlock generateCustomizerMap() {
+ Builder code = CodeBlock.builder();
+ code.addStatement("$T<$T, $T> $L = new $T<>()", MultiValueMap.class, String.class, BeanDefinitionCustomizer.class,
+ CUSTOMIZER_MAP_VARIABLE, LinkedMultiValueMap.class);
+ return code.build();
+ }
+
+ private CodeBlock generateRegisterCode() {
+ Builder code = CodeBlock.builder();
+ for (BeanRegistrar beanRegistrar : this.beanRegistrars) {
+ code.addStatement("new $T().register(new $T(($T)$L, $L, $T.class, $L), $L)", beanRegistrar.getClass(),
+ BeanRegistryAdapter.class, BeanDefinitionRegistry.class, BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE,
+ BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE, beanRegistrar.getClass(), CUSTOMIZER_MAP_VARIABLE,
+ ENVIRONMENT_VARIABLE);
+ }
+ return code.build();
+ }
+
+ private CodeBlock generateInitDestroyMethods(String beanName, AbstractBeanDefinition beanDefinition,
+ String[] methodNames, String method, ReflectionHints reflectionHints) {
+
+ Builder code = CodeBlock.builder();
+ // For Publisher-based destroy methods
+ reflectionHints.registerType(TypeReference.of("org.reactivestreams.Publisher"));
+ Class> beanType = ClassUtils.getUserClass(beanDefinition.getResolvableType().toClass());
+ Arrays.stream(methodNames).forEach(methodName -> addInitDestroyHint(beanType, methodName, reflectionHints));
+ CodeBlock arguments = Arrays.stream(methodNames)
+ .map(name -> CodeBlock.of("$S", name))
+ .collect(CodeBlock.joining(", "));
+
+ code.addStatement("$L.add($S, $L -> (($T)$L).$L($L))", CUSTOMIZER_MAP_VARIABLE, beanName, "bd",
+ AbstractBeanDefinition.class, "bd", method, arguments);
+ return code.build();
+ }
+
+ // Inspired from BeanDefinitionPropertiesCodeGenerator#addInitDestroyHint
+ private static void addInitDestroyHint(Class> beanUserClass, String methodName, ReflectionHints reflectionHints) {
+ Class> methodDeclaringClass = beanUserClass;
+
+ // Parse fully-qualified method name if necessary.
+ int indexOfDot = methodName.lastIndexOf('.');
+ if (indexOfDot > 0) {
+ String className = methodName.substring(0, indexOfDot);
+ methodName = methodName.substring(indexOfDot + 1);
+ if (!beanUserClass.getName().equals(className)) {
+ try {
+ methodDeclaringClass = ClassUtils.forName(className, beanUserClass.getClassLoader());
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Failed to load Class [" + className +
+ "] from ClassLoader [" + beanUserClass.getClassLoader() + "]", ex);
+ }
+ }
+ }
+
+ Method method = ReflectionUtils.findMethod(methodDeclaringClass, methodName);
+ if (method != null) {
+ reflectionHints.registerMethod(method, ExecutableMode.INVOKE);
+ Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(method, beanUserClass);
+ if (!publiclyAccessibleMethod.equals(method)) {
+ reflectionHints.registerMethod(publiclyAccessibleMethod, ExecutableMode.INVOKE);
+ }
+ }
+ }
+
+
+ static class UnsupportedBeanRegistrationCode implements BeanRegistrationCode {
+
+ private final String message;
+
+ public UnsupportedBeanRegistrationCode(String beanName, Class> aotProcessorClass) {
+ this.message = "Code generation attempted for bean " + beanName + " by the AOT Processor " +
+ aotProcessorClass + " is not supported with BeanRegistrar yet";
+ }
+
+ @Override
+ public ClassName getClassName() {
+ throw new UnsupportedOperationException(this.message);
+ }
+
+ @Override
+ public GeneratedMethods getMethods() {
+ throw new UnsupportedOperationException(this.message);
+ }
+
+ @Override
+ public void addInstancePostProcessor(MethodReference methodReference) {
+ throw new UnsupportedOperationException(this.message);
+ }
+ }
+ }
+
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Import.java b/spring-context/src/main/java/org/springframework/context/annotation/Import.java
index 9c905d0d0b..c1275801fc 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/Import.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/Import.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -22,6 +22,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import org.springframework.beans.factory.BeanRegistrar;
+
/**
* Indicates one or more component classes to import — typically
* {@link Configuration @Configuration} classes.
@@ -57,7 +59,8 @@ public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
- * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
+ * {@link ImportBeanDefinitionRegistrar}, {@link BeanRegistrar} or regular
+ * component classes to import.
*/
Class>[] value();
diff --git a/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextInitializationCodeGenerator.java b/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextInitializationCodeGenerator.java
index 6f52e00448..0b6a6fda54 100644
--- a/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextInitializationCodeGenerator.java
+++ b/spring-context/src/main/java/org/springframework/context/aot/ApplicationContextInitializationCodeGenerator.java
@@ -30,6 +30,7 @@ import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
+import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
@@ -150,6 +151,7 @@ class ApplicationContextInitializationCodeGenerator implements BeanFactoryInitia
private @Nullable CodeBlock apply(ClassName className) {
String name = className.canonicalName();
if (name.equals(DefaultListableBeanFactory.class.getName())
+ || name.equals(ListableBeanFactory.class.getName())
|| name.equals(ConfigurableListableBeanFactory.class.getName())) {
return CodeBlock.of(BEAN_FACTORY_VARIABLE);
}
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java
index 7d238cb6a2..089ee600b2 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -16,6 +16,7 @@
package org.springframework.context.annotation;
+import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
@@ -24,6 +25,7 @@ import java.util.function.Predicate;
import javax.lang.model.element.Modifier;
+import jakarta.annotation.PostConstruct;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Nested;
@@ -37,7 +39,10 @@ import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanRegistrar;
+import org.springframework.beans.factory.BeanRegistry;
import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
@@ -54,6 +59,7 @@ import org.springframework.context.testfixture.context.annotation.SimpleConfigur
import org.springframework.context.testfixture.context.generator.SimpleComponent;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.test.tools.Compiled;
@@ -74,8 +80,9 @@ import static org.assertj.core.api.Assertions.entry;
* @author Phillip Webb
* @author Stephane Nicoll
* @author Sam Brannen
+ * @author Sebastien Deleuze
*/
-class ConfigurationClassPostProcessorAotContributionTests {
+public class ConfigurationClassPostProcessorAotContributionTests {
private final TestGenerationContext generationContext = new TestGenerationContext();
@@ -439,6 +446,135 @@ class ConfigurationClassPostProcessorAotContributionTests {
}
}
+ @Nested
+ public class BeanRegistrarTests {
+
+ @Test
+ void applyToWhenHasDefaultConstructor() throws NoSuchMethodException {
+ BeanFactoryInitializationAotContribution contribution = getContribution(DefaultConstructorConfiguration.class);
+ assertThat(contribution).isNotNull();
+ contribution.applyTo(generationContext, beanFactoryInitializationCode);
+ Constructor fooConstructor = Foo.class.getDeclaredConstructor();
+ compile((initializer, compiled) -> {
+ GenericApplicationContext freshContext = new GenericApplicationContext();
+ initializer.accept(freshContext);
+ freshContext.refresh();
+ assertThat(freshContext.getBean(Foo.class)).isNotNull();
+ assertThat(RuntimeHintsPredicates.reflection().onConstructorInvocation(fooConstructor))
+ .accepts(generationContext.getRuntimeHints());
+ freshContext.close();
+ });
+ }
+
+ @Test
+ void applyToWhenHasInstanceSupplier() {
+ BeanFactoryInitializationAotContribution contribution = getContribution(InstanceSupplierConfiguration.class);
+ assertThat(contribution).isNotNull();
+ contribution.applyTo(generationContext, beanFactoryInitializationCode);
+ compile((initializer, compiled) -> {
+ GenericApplicationContext freshContext = new GenericApplicationContext();
+ initializer.accept(freshContext);
+ freshContext.refresh();
+ assertThat(freshContext.getBean(Foo.class)).isNotNull();
+ assertThat(generationContext.getRuntimeHints().reflection().getTypeHint(Foo.class)).isNull();
+ freshContext.close();
+ });
+ }
+
+ @Test
+ void applyToWhenHasPostConstructAnnotationPostProcessed() {
+ BeanFactoryInitializationAotContribution contribution = getContribution(CommonAnnotationBeanPostProcessor.class,
+ PostConstructConfiguration.class);
+ assertThat(contribution).isNotNull();
+ contribution.applyTo(generationContext, beanFactoryInitializationCode);
+ compile((initializer, compiled) -> {
+ GenericApplicationContext freshContext = new GenericApplicationContext();
+ initializer.accept(freshContext);
+ freshContext.refresh();
+ Init init = freshContext.getBean(Init.class);
+ assertThat(init).isNotNull();
+ assertThat(init.initialized).isTrue();
+ assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(Init.class, "postConstruct"))
+ .accepts(generationContext.getRuntimeHints());
+ freshContext.close();
+ });
+ }
+
+ private void compile(BiConsumer, Compiled> result) {
+ MethodReference methodReference = beanFactoryInitializationCode.getInitializers().get(0);
+ beanFactoryInitializationCode.getTypeBuilder().set(type -> {
+ ArgumentCodeGenerator argCodeGenerator = ArgumentCodeGenerator
+ .of(ListableBeanFactory.class, "applicationContext.getBeanFactory()")
+ .and(ArgumentCodeGenerator.of(Environment.class, "applicationContext.getEnvironment()"));
+ CodeBlock methodInvocation = methodReference.toInvokeCodeBlock(argCodeGenerator,
+ beanFactoryInitializationCode.getClassName());
+ type.addModifiers(Modifier.PUBLIC);
+ type.addSuperinterface(ParameterizedTypeName.get(Consumer.class, GenericApplicationContext.class));
+ type.addMethod(MethodSpec.methodBuilder("accept").addModifiers(Modifier.PUBLIC)
+ .addParameter(GenericApplicationContext.class, "applicationContext")
+ .addStatement(methodInvocation)
+ .build());
+ });
+ generationContext.writeGeneratedContent();
+ TestCompiler.forSystem().with(generationContext).compile(compiled ->
+ result.accept(compiled.getInstance(Consumer.class), compiled));
+ }
+
+
+ @Configuration
+ @Import(DefaultConstructorBeanRegistrar.class)
+ public static class DefaultConstructorConfiguration {
+ }
+
+ public static class DefaultConstructorBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean(Foo.class);
+ }
+ }
+
+ @Configuration
+ @Import(InstanceSupplierBeanRegistrar.class)
+ public static class InstanceSupplierConfiguration {
+ }
+
+ public static class InstanceSupplierBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean(Foo.class, spec -> spec.supplier(context -> new Foo()));
+ }
+ }
+
+ @Configuration
+ @Import(PostConstructBeanRegistrar.class)
+ public static class PostConstructConfiguration {
+ }
+
+ public static class PostConstructBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean(Init.class);
+ }
+ }
+
+ static class Foo {
+ }
+
+ static class Init {
+
+ boolean initialized = false;
+
+ @PostConstruct
+ void postConstruct() {
+ initialized = true;
+ }
+ }
+
+ }
+
private @Nullable BeanFactoryInitializationAotContribution getContribution(Class>... types) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/beanregistrar/BeanRegistrarConfigurationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/beanregistrar/BeanRegistrarConfigurationTests.java
new file mode 100644
index 0000000000..5cbbb4e44c
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/context/annotation/beanregistrar/BeanRegistrarConfigurationTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2025 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.annotation.beanregistrar;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.BeanRegistrar;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Bar;
+import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Baz;
+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 static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for {@link BeanRegistrar} imported by @{@link org.springframework.context.annotation.Configuration}.
+ *
+ * @author Sebastien Deleuze
+ */
+public class BeanRegistrarConfigurationTests {
+
+ @Test
+ void beanRegistrar() {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanRegistrarConfiguration.class);
+ assertThat(context.getBean(Bar.class).foo()).isEqualTo(context.getBean(Foo.class));
+ assertThatThrownBy(() -> context.getBean(Baz.class)).isInstanceOf(NoSuchBeanDefinitionException.class);
+ assertThat(context.getBean(Init.class).initialized).isTrue();
+ BeanDefinition beanDefinition = context.getBeanDefinition("bar");
+ assertThat(beanDefinition.getScope()).isEqualTo(BeanDefinition.SCOPE_PROTOTYPE);
+ assertThat(beanDefinition.isLazyInit()).isTrue();
+ assertThat(beanDefinition.getDescription()).isEqualTo("Custom description");
+ }
+
+ @Test
+ void beanRegistrarWithProfile() {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.register(BeanRegistrarConfiguration.class);
+ context.getEnvironment().addActiveProfile("baz");
+ context.refresh();
+ assertThat(context.getBean(Baz.class).message()).isEqualTo("Hello World!");
+ }
+
+ @Test
+ void scannedFunctionalConfiguration() {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.scan("org.springframework.context.testfixture.context.annotation.registrar");
+ context.refresh();
+ assertThat(context.getBean(Bar.class).foo()).isEqualTo(context.getBean(Foo.class));
+ assertThatThrownBy(() -> context.getBean(Baz.class).message()).isInstanceOf(NoSuchBeanDefinitionException.class);
+ assertThat(context.getBean(Init.class).initialized).isTrue();
+ }
+
+}
diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/SampleBeanRegistrar.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/SampleBeanRegistrar.java
new file mode 100644
index 0000000000..26115c7d77
--- /dev/null
+++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/SampleBeanRegistrar.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2025 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 jakarta.annotation.PostConstruct;
+
+import org.springframework.beans.factory.BeanRegistrar;
+import org.springframework.beans.factory.BeanRegistry;
+import org.springframework.core.env.Environment;
+
+public class SampleBeanRegistrar implements BeanRegistrar {
+
+ @Override
+ public void register(BeanRegistry registry, Environment env) {
+ registry.registerBean("foo", Foo.class);
+ registry.registerBean("bar", Bar.class, spec -> spec
+ .prototype()
+ .lazyInit()
+ .description("Custom description")
+ .supplier(context -> new Bar(context.bean(Foo.class))));
+ if (env.matchesProfiles("baz")) {
+ registry.registerBean(Baz.class, spec -> spec
+ .supplier(context -> new Baz("Hello World!")));
+ }
+ registry.registerBean(Init.class);
+ }
+
+ public record Foo() {}
+ public record Bar(Foo foo) {}
+ public record Baz(String message) {}
+
+ public static class Init {
+
+ public boolean initialized = false;
+
+ @PostConstruct
+ public void postConstruct() {
+ initialized = true;
+ }
+ }
+}
diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/registrar/BeanRegistrarConfiguration.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/registrar/BeanRegistrarConfiguration.java
new file mode 100644
index 0000000000..1360caec7e
--- /dev/null
+++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/registrar/BeanRegistrarConfiguration.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2002-2025 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.SampleBeanRegistrar;
+
+@Configuration
+@Import(SampleBeanRegistrar.class)
+public class BeanRegistrarConfiguration {
+}