Skip to content

Commit 496be9c

Browse files
committed
Introduce first-class support for programmatic bean registration
This commit introduces a new BeanRegistrar interface that can be implemented to register beans programmatically in a concise and flexible way. Those bean registrar implementations are typically imported with an `@Import` annotation on `@Configuration` classes. See BeanRegistrarConfigurationTests for a concrete example. See spring-projectsgh-18353
1 parent aeaf52e commit 496be9c

File tree

14 files changed

+1356
-8
lines changed

14 files changed

+1356
-8
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.factory;
18+
19+
import org.springframework.core.env.Environment;
20+
21+
/**
22+
* Contract for registering beans programmatically.
23+
*
24+
* <p>Typically imported with an {@link org.springframework.context.annotation.Import @Import}
25+
* annotation on {@link org.springframework.context.annotation.Configuration @Configuration}
26+
* classes.
27+
* <pre class="code">
28+
* &#064;Configuration
29+
* &#064;Import(MyBeanRegistrar.class)
30+
* class MyConfiguration {
31+
* }</pre>
32+
*
33+
* <p>The bean registrar implementation uses {@link BeanRegistry} and {@link Environment}
34+
* APIs to register beans programmatically in a concise and flexible way.
35+
* <pre class="code">
36+
* class MyBeanRegistrar implements BeanRegistrar {
37+
*
38+
* &#064;Override
39+
* public void register(BeanRegistry registry, Environment env) {
40+
* registry.registerBean("foo", Foo.class);
41+
* registry.registerBean("bar", Bar.class, spec -> spec
42+
* .prototype()
43+
* .lazyInit()
44+
* .description("Custom description")
45+
* .supplier(context -> new Bar(context.bean(Foo.class))));
46+
* if (env.matchesProfiles("baz")) {
47+
* registry.registerBean(Baz.class, spec -> spec
48+
* .supplier(context -> new Baz("Hello World!")));
49+
* }
50+
* }
51+
* }</pre>
52+
*
53+
* <p>In Kotlin, it is recommended to use {@code BeanRegistrarDsl} instead of
54+
* implementing {@code BeanRegistrar}.
55+
*
56+
* @author Sebastien Deleuze
57+
* @since 7.0
58+
*/
59+
@FunctionalInterface
60+
public interface BeanRegistrar {
61+
62+
/**
63+
* Register beans in a programmatic way.
64+
* @param registry the bean registry
65+
* @param env the environment that can be used to get the active profile or some properties
66+
*/
67+
void register(BeanRegistry registry, Environment env);
68+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.factory;
18+
19+
import java.util.function.Consumer;
20+
import java.util.function.Function;
21+
import java.util.function.Supplier;
22+
23+
import org.springframework.beans.BeanUtils;
24+
import org.springframework.beans.BeansException;
25+
import org.springframework.beans.factory.config.BeanDefinition;
26+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
27+
import org.springframework.core.ResolvableType;
28+
import org.springframework.core.env.Environment;
29+
30+
/**
31+
* Used in {@link BeanRegistrar#register(BeanRegistry, Environment)} to expose
32+
* programmatic bean registration capabilities.
33+
*
34+
* @author Sebastien Deleuze
35+
* @since 7.0
36+
*/
37+
public interface BeanRegistry {
38+
39+
/**
40+
* Register a bean from the given bean class, which will be instantiated
41+
* using the related {@link BeanUtils#getResolvableConstructor resolvable constructor}
42+
* if any.
43+
* @param beanClass the class of the bean
44+
* @return the generated bean name
45+
*/
46+
<T> String registerBean(Class<T> beanClass);
47+
48+
/**
49+
* Register a bean from the given bean class, customizing it with the customizer
50+
* callback. The bean will be instantiated using the supplier that can be
51+
* configured in the customizer callback, or will be tentatively instantiated
52+
* with its {@link BeanUtils#getResolvableConstructor resolvable constructor}
53+
* otherwise.
54+
* @param beanClass the class of the bean
55+
* @param customizer callback to customize other bean properties than the name
56+
* @return the generated bean name
57+
*/
58+
<T> String registerBean(Class<T> beanClass, Consumer<Spec<T>> customizer);
59+
60+
/**
61+
* Register a bean from the given bean class, which will be instantiated
62+
* using the related {@link BeanUtils#getResolvableConstructor resolvable constructor}
63+
* if any.
64+
* @param name the name of the bean
65+
* @param beanClass the class of the bean
66+
*/
67+
<T> void registerBean(String name, Class<T> beanClass);
68+
69+
/**
70+
* Register a bean from the given bean class, customizing it with the customizer
71+
* callback. The bean will be instantiated using the supplier that can be
72+
* configured in the customizer callback, or will be tentatively instantiated with its
73+
* {@link BeanUtils#getResolvableConstructor resolvable constructor} otherwise.
74+
* @param name the name of the bean
75+
* @param beanClass the class of the bean
76+
* @param customizer callback to customize other bean properties than the name
77+
*/
78+
<T> void registerBean(String name, Class<T> beanClass, Consumer<Spec<T>> customizer);
79+
80+
/**
81+
* Specification for customizing a bean.
82+
* @param <T> the bean type
83+
*/
84+
interface Spec<T> {
85+
86+
/**
87+
* Allow for instantiating this bean on a background thread.
88+
* @see AbstractBeanDefinition#setBackgroundInit(boolean)
89+
*/
90+
Spec<T> backgroundInit();
91+
92+
/**
93+
* Set a human-readable description of this bean.
94+
* @see BeanDefinition#setDescription(String)
95+
*/
96+
Spec<T> description(String description);
97+
98+
/**
99+
* Configure this bean as a fallback autowire candidate.
100+
* @see BeanDefinition#setFallback(boolean)
101+
* @see #primary
102+
*/
103+
Spec<T> fallback();
104+
105+
/**
106+
* Hint that this bean has an infrastructure role, meaning it has no
107+
* relevance to the end-user.
108+
* @see BeanDefinition#setRole(int)
109+
* @see BeanDefinition#ROLE_INFRASTRUCTURE
110+
*/
111+
Spec<T> infrastructure();
112+
113+
/**
114+
* Configure this bean as lazily initialized.
115+
* @see BeanDefinition#setLazyInit(boolean)
116+
*/
117+
Spec<T> lazyInit();
118+
119+
/**
120+
* Configure this bean as not a candidate for getting autowired into some
121+
* other bean.
122+
* @see BeanDefinition#setAutowireCandidate(boolean)
123+
*/
124+
Spec<T> notAutowirable();
125+
126+
/**
127+
* The sort order of this bean. This is analogous to the
128+
* {@code @Order} annotation.
129+
* @see AbstractBeanDefinition#ORDER_ATTRIBUTE
130+
*/
131+
Spec<T> order(int order);
132+
133+
/**
134+
* Configure this bean as a primary autowire candidate.
135+
* @see BeanDefinition#setPrimary(boolean)
136+
* @see #fallback
137+
*/
138+
Spec<T> primary();
139+
140+
/**
141+
* Configure this bean with a prototype scope.
142+
* @see BeanDefinition#setScope(String)
143+
* @see BeanDefinition#SCOPE_PROTOTYPE
144+
*/
145+
Spec<T> prototype();
146+
147+
/**
148+
* Set the supplier to construct a bean instance.
149+
* @see AbstractBeanDefinition#setInstanceSupplier(Supplier)
150+
*/
151+
Spec<T> supplier(Function<SupplierContext, T> supplier);
152+
}
153+
154+
/**
155+
* Context available from the bean instance supplier designed to give access
156+
* to bean dependencies.
157+
*/
158+
interface SupplierContext {
159+
160+
/**
161+
* Return the bean instance that uniquely matches the given object type,
162+
* if any.
163+
* @param requiredType type the bean must match; can be an interface or
164+
* superclass
165+
* @return an instance of the single bean matching the required type
166+
* @see BeanFactory#getBean(String)
167+
*/
168+
<T> T bean(Class<T> requiredType) throws BeansException;
169+
170+
/**
171+
* Return an instance, which may be shared or independent, of the
172+
* specified bean.
173+
* @param name the name of the bean to retrieve
174+
* @param requiredType type the bean must match; can be an interface or superclass
175+
* @return an instance of the bean.
176+
* @see BeanFactory#getBean(String, Class)
177+
*/
178+
<T> T bean(String name, Class<T> requiredType) throws BeansException;
179+
180+
/**
181+
* Return a provider for the specified bean, allowing for lazy on-demand retrieval
182+
* of instances, including availability and uniqueness options.
183+
* <p>For matching a generic type, consider {@link #beanProvider(ResolvableType)}.
184+
* @param requiredType type the bean must match; can be an interface or superclass
185+
* @return a corresponding provider handle
186+
* @see BeanFactory#getBeanProvider(Class)
187+
*/
188+
<T> ObjectProvider<T> beanProvider(Class<T> requiredType);
189+
190+
/**
191+
* Return a provider for the specified bean, allowing for lazy on-demand retrieval
192+
* of instances, including availability and uniqueness options. This variant allows
193+
* for specifying a generic type to match, similar to reflective injection points
194+
* with generic type declarations in method/constructor parameters.
195+
* <p>Note that collections of beans are not supported here, in contrast to reflective
196+
* injection points. For programmatically retrieving a list of beans matching a
197+
* specific type, specify the actual bean type as an argument here and subsequently
198+
* use {@link ObjectProvider#orderedStream()} or its lazy streaming/iteration options.
199+
* <p>Also, generics matching is strict here, as per the Java assignment rules.
200+
* For lenient fallback matching with unchecked semantics (similar to the 'unchecked'
201+
* Java compiler warning), consider calling {@link #beanProvider(Class)} with the
202+
* raw type as a second step if no full generic match is
203+
* {@link ObjectProvider#getIfAvailable() available} with this variant.
204+
* @param requiredType type the bean must match; can be a generic type declaration
205+
* @return a corresponding provider handle
206+
* @see BeanFactory#getBeanProvider(ResolvableType)
207+
*/
208+
<T> ObjectProvider<T> beanProvider(ResolvableType requiredType);
209+
}
210+
}

0 commit comments

Comments
 (0)