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 index cc05c62c804..dd8ac536541 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistrar.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistrar.java @@ -60,9 +60,10 @@ import org.springframework.core.env.Environment; public interface BeanRegistrar { /** - * Register beans in a programmatic way. - * @param registry the bean registry + * Register beans on the given {@link BeanRegistry} in a programmatic way. + * @param registry the bean registry to operate on * @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/support/BeanRegistryAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanRegistryAdapter.java index da1073f3884..30533668365 100644 --- 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 @@ -56,6 +56,12 @@ public class BeanRegistryAdapter implements BeanRegistry { private final @Nullable MultiValueMap customizers; + public BeanRegistryAdapter(DefaultListableBeanFactory beanFactory, Environment environment, + Class beanRegistrarClass) { + + this(beanFactory, beanFactory, environment, beanRegistrarClass, null); + } + public BeanRegistryAdapter(BeanDefinitionRegistry beanRegistry, ListableBeanFactory beanFactory, Environment environment, Class beanRegistrarClass) { @@ -73,6 +79,7 @@ public class BeanRegistryAdapter implements BeanRegistry { this.customizers = customizers; } + @Override public String registerBean(Class beanClass) { String beanName = BeanDefinitionReaderUtils.uniqueBeanName(beanClass.getName(), this.beanRegistry); @@ -154,7 +161,8 @@ public class BeanRegistryAdapter implements BeanRegistry { } } - static class BeanSpecAdapter implements Spec { + + private static class BeanSpecAdapter implements Spec { private final RootBeanDefinition beanDefinition; @@ -239,7 +247,8 @@ public class BeanRegistryAdapter implements BeanRegistry { } } - static class SupplierContextAdapter implements SupplierContext { + + private static class SupplierContextAdapter implements SupplierContext { private final ListableBeanFactory beanFactory; diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigRegistry.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigRegistry.java index 4cac35bb999..3210fa18bfc 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigRegistry.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigRegistry.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. @@ -16,6 +16,8 @@ package org.springframework.context.annotation; +import org.springframework.beans.factory.BeanRegistrar; + /** * Common interface for annotation config application contexts, * defining {@link #register} and {@link #scan} methods. @@ -26,17 +28,33 @@ package org.springframework.context.annotation; public interface AnnotationConfigRegistry { /** - * Register one or more component classes to be processed. + * Invoke the given registrars for registering their beans with this + * application context. + *

This can be used to register custom beans without inferring + * annotation-based characteristics for primary/fallback/lazy-init, + * rather specifying those programmatically if needed. + * @param registrars one or more {@link BeanRegistrar} instances + * @since 7.0 + * @see #register(Class[]) + */ + void register(BeanRegistrar... registrars); + + /** + * Register one or more component classes to be processed, inferring + * annotation-based characteristics for primary/fallback/lazy-init + * just like for scanned component classes. *

Calls to {@code register} are idempotent; adding the same * component class more than once has no additional effect. * @param componentClasses one or more component classes, * for example, {@link Configuration @Configuration} classes + * @see #scan(String...) */ void register(Class... componentClasses); /** * Perform a scan within the specified base packages. * @param basePackages the packages to scan for component classes + * @see #register(Class[]) */ void scan(String... basePackages); diff --git a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java index f318d1eb235..5a8ae4b2b9e 100644 --- a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java @@ -30,6 +30,7 @@ import org.springframework.aot.hint.support.ClassHintUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanRegistrar; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; @@ -38,6 +39,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.BeanRegistryAdapter; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -594,6 +596,21 @@ public class GenericApplicationContext extends AbstractApplicationContext implem registerBeanDefinition(nameToUse, beanDefinition); } + /** + * Invoke the given registrars for registering their beans with this + * application context. + *

This can be used to apply encapsulated pieces of programmatic + * bean registration to this application context without relying on + * individual calls to its context-level {@code registerBean} methods. + * @param registrars one or more {@link BeanRegistrar} instances + * @since 7.0 + */ + public void register(BeanRegistrar... registrars) { + for (BeanRegistrar registrar : registrars) { + new BeanRegistryAdapter(this.beanFactory, getEnvironment(), registrar.getClass()).register(registrar); + } + } + /** * {@link RootBeanDefinition} subclass for {@code #registerBean} based diff --git a/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java index feee8995f4b..4b5887509b0 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 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,7 +22,9 @@ import java.util.Set; import org.jspecify.annotations.Nullable; +import org.springframework.beans.factory.BeanRegistrar; import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.beans.factory.support.BeanRegistryAdapter; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.AnnotationConfigRegistry; @@ -104,6 +106,8 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe private @Nullable ScopeMetadataResolver scopeMetadataResolver; + private final Set beanRegistrars = new LinkedHashSet<>(); + private final Set> componentClasses = new LinkedHashSet<>(); private final Set basePackages = new LinkedHashSet<>(); @@ -148,6 +152,20 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe } + /** + * Invoke the given registrars for registering their beans with this + * application context. + *

Note that {@link #refresh()} must be called in order for the context + * to fully process the new classes. + * @param registrars one or more {@link BeanRegistrar} instances + * @since 7.0 + */ + @Override + public void register(BeanRegistrar... registrars) { + Assert.notEmpty(registrars, "At least one BeanRegistrar must be specified"); + Collections.addAll(this.beanRegistrars, registrars); + } + /** * Register one or more component classes to be processed. *

Note that {@link #refresh()} must be called in order for the context @@ -222,6 +240,16 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe scanner.setScopeMetadataResolver(scopeMetadataResolver); } + if (!this.beanRegistrars.isEmpty()) { + if (logger.isDebugEnabled()) { + logger.debug("Applying bean registrars: [" + + StringUtils.collectionToCommaDelimitedString(this.beanRegistrars) + "]"); + } + for (BeanRegistrar registrar : this.beanRegistrars) { + new BeanRegistryAdapter(beanFactory, getEnvironment(), registrar.getClass()).register(registrar); + } + } + if (!this.componentClasses.isEmpty()) { if (logger.isDebugEnabled()) { logger.debug("Registering component classes: [" +