Skip to content

Commit 86b21d9

Browse files
committed
Add support for BeanRegistrar registration on GenericApplicationContext
Closes gh-34574
1 parent beb3a91 commit 86b21d9

File tree

5 files changed

+80
-7
lines changed

5 files changed

+80
-7
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistrar.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@
6060
public interface BeanRegistrar {
6161

6262
/**
63-
* Register beans in a programmatic way.
64-
* @param registry the bean registry
63+
* Register beans on the given {@link BeanRegistry} in a programmatic way.
64+
* @param registry the bean registry to operate on
6565
* @param env the environment that can be used to get the active profile or some properties
6666
*/
6767
void register(BeanRegistry registry, Environment env);
68+
6869
}

spring-beans/src/main/java/org/springframework/beans/factory/support/BeanRegistryAdapter.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ public class BeanRegistryAdapter implements BeanRegistry {
5656
private final @Nullable MultiValueMap<String, BeanDefinitionCustomizer> customizers;
5757

5858

59+
public BeanRegistryAdapter(DefaultListableBeanFactory beanFactory, Environment environment,
60+
Class<? extends BeanRegistrar> beanRegistrarClass) {
61+
62+
this(beanFactory, beanFactory, environment, beanRegistrarClass, null);
63+
}
64+
5965
public BeanRegistryAdapter(BeanDefinitionRegistry beanRegistry, ListableBeanFactory beanFactory,
6066
Environment environment, Class<? extends BeanRegistrar> beanRegistrarClass) {
6167

@@ -73,6 +79,7 @@ public BeanRegistryAdapter(BeanDefinitionRegistry beanRegistry, ListableBeanFact
7379
this.customizers = customizers;
7480
}
7581

82+
7683
@Override
7784
public <T> String registerBean(Class<T> beanClass) {
7885
String beanName = BeanDefinitionReaderUtils.uniqueBeanName(beanClass.getName(), this.beanRegistry);
@@ -154,7 +161,8 @@ public RootBeanDefinition cloneBeanDefinition() {
154161
}
155162
}
156163

157-
static class BeanSpecAdapter<T> implements Spec<T> {
164+
165+
private static class BeanSpecAdapter<T> implements Spec<T> {
158166

159167
private final RootBeanDefinition beanDefinition;
160168

@@ -239,7 +247,8 @@ public Spec<T> targetType(ResolvableType targetType) {
239247
}
240248
}
241249

242-
static class SupplierContextAdapter implements SupplierContext {
250+
251+
private static class SupplierContextAdapter implements SupplierContext {
243252

244253
private final ListableBeanFactory beanFactory;
245254

spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigRegistry.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.context.annotation;
1818

19+
import org.springframework.beans.factory.BeanRegistrar;
20+
1921
/**
2022
* Common interface for annotation config application contexts,
2123
* defining {@link #register} and {@link #scan} methods.
@@ -26,17 +28,33 @@
2628
public interface AnnotationConfigRegistry {
2729

2830
/**
29-
* Register one or more component classes to be processed.
31+
* Invoke the given registrars for registering their beans with this
32+
* application context.
33+
* <p>This can be used to register custom beans without inferring
34+
* annotation-based characteristics for primary/fallback/lazy-init,
35+
* rather specifying those programmatically if needed.
36+
* @param registrars one or more {@link BeanRegistrar} instances
37+
* @since 7.0
38+
* @see #register(Class[])
39+
*/
40+
void register(BeanRegistrar... registrars);
41+
42+
/**
43+
* Register one or more component classes to be processed, inferring
44+
* annotation-based characteristics for primary/fallback/lazy-init
45+
* just like for scanned component classes.
3046
* <p>Calls to {@code register} are idempotent; adding the same
3147
* component class more than once has no additional effect.
3248
* @param componentClasses one or more component classes,
3349
* for example, {@link Configuration @Configuration} classes
50+
* @see #scan(String...)
3451
*/
3552
void register(Class<?>... componentClasses);
3653

3754
/**
3855
* Perform a scan within the specified base packages.
3956
* @param basePackages the packages to scan for component classes
57+
* @see #register(Class[])
4058
*/
4159
void scan(String... basePackages);
4260

spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java

+17
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.beans.BeanUtils;
3131
import org.springframework.beans.BeansException;
3232
import org.springframework.beans.factory.BeanDefinitionStoreException;
33+
import org.springframework.beans.factory.BeanRegistrar;
3334
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3435
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
3536
import org.springframework.beans.factory.config.BeanDefinition;
@@ -38,6 +39,7 @@
3839
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
3940
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
4041
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
42+
import org.springframework.beans.factory.support.BeanRegistryAdapter;
4143
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
4244
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
4345
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -594,6 +596,21 @@ public <T> void registerBean(@Nullable String beanName, Class<T> beanClass,
594596
registerBeanDefinition(nameToUse, beanDefinition);
595597
}
596598

599+
/**
600+
* Invoke the given registrars for registering their beans with this
601+
* application context.
602+
* <p>This can be used to apply encapsulated pieces of programmatic
603+
* bean registration to this application context without relying on
604+
* individual calls to its context-level {@code registerBean} methods.
605+
* @param registrars one or more {@link BeanRegistrar} instances
606+
* @since 7.0
607+
*/
608+
public void register(BeanRegistrar... registrars) {
609+
for (BeanRegistrar registrar : registrars) {
610+
new BeanRegistryAdapter(this.beanFactory, getEnvironment(), registrar.getClass()).register(registrar);
611+
}
612+
}
613+
597614

598615
/**
599616
* {@link RootBeanDefinition} subclass for {@code #registerBean} based

spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,7 +22,9 @@
2222

2323
import org.jspecify.annotations.Nullable;
2424

25+
import org.springframework.beans.factory.BeanRegistrar;
2526
import org.springframework.beans.factory.support.BeanNameGenerator;
27+
import org.springframework.beans.factory.support.BeanRegistryAdapter;
2628
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
2729
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
2830
import org.springframework.context.annotation.AnnotationConfigRegistry;
@@ -104,6 +106,8 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe
104106

105107
private @Nullable ScopeMetadataResolver scopeMetadataResolver;
106108

109+
private final Set<BeanRegistrar> beanRegistrars = new LinkedHashSet<>();
110+
107111
private final Set<Class<?>> componentClasses = new LinkedHashSet<>();
108112

109113
private final Set<String> basePackages = new LinkedHashSet<>();
@@ -148,6 +152,20 @@ public void setScopeMetadataResolver(@Nullable ScopeMetadataResolver scopeMetada
148152
}
149153

150154

155+
/**
156+
* Invoke the given registrars for registering their beans with this
157+
* application context.
158+
* <p>Note that {@link #refresh()} must be called in order for the context
159+
* to fully process the new classes.
160+
* @param registrars one or more {@link BeanRegistrar} instances
161+
* @since 7.0
162+
*/
163+
@Override
164+
public void register(BeanRegistrar... registrars) {
165+
Assert.notEmpty(registrars, "At least one BeanRegistrar must be specified");
166+
Collections.addAll(this.beanRegistrars, registrars);
167+
}
168+
151169
/**
152170
* Register one or more component classes to be processed.
153171
* <p>Note that {@link #refresh()} must be called in order for the context
@@ -222,6 +240,16 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
222240
scanner.setScopeMetadataResolver(scopeMetadataResolver);
223241
}
224242

243+
if (!this.beanRegistrars.isEmpty()) {
244+
if (logger.isDebugEnabled()) {
245+
logger.debug("Applying bean registrars: [" +
246+
StringUtils.collectionToCommaDelimitedString(this.beanRegistrars) + "]");
247+
}
248+
for (BeanRegistrar registrar : this.beanRegistrars) {
249+
new BeanRegistryAdapter(beanFactory, getEnvironment(), registrar.getClass()).register(registrar);
250+
}
251+
}
252+
225253
if (!this.componentClasses.isEmpty()) {
226254
if (logger.isDebugEnabled()) {
227255
logger.debug("Registering component classes: [" +

0 commit comments

Comments
 (0)