Skip to content

Commit a0e2d3a

Browse files
committed
Add support for target type to BeanRegistry
Closes gh-34560
1 parent c74f897 commit a0e2d3a

File tree

8 files changed

+173
-1
lines changed

8 files changed

+173
-1
lines changed

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

+16
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.springframework.beans.BeansException;
2525
import org.springframework.beans.factory.config.BeanDefinition;
2626
import org.springframework.beans.factory.support.AbstractBeanDefinition;
27+
import org.springframework.beans.factory.support.RootBeanDefinition;
28+
import org.springframework.core.ParameterizedTypeReference;
2729
import org.springframework.core.ResolvableType;
2830
import org.springframework.core.env.Environment;
2931

@@ -149,6 +151,20 @@ interface Spec<T> {
149151
* @see AbstractBeanDefinition#setInstanceSupplier(Supplier)
150152
*/
151153
Spec<T> supplier(Function<SupplierContext, T> supplier);
154+
155+
/**
156+
* Set a generics-containing target type of this bean.
157+
* @see #targetType(ResolvableType)
158+
* @see RootBeanDefinition#setTargetType(ResolvableType)
159+
*/
160+
Spec<T> targetType(ParameterizedTypeReference<? extends T> type);
161+
162+
/**
163+
* Set a generics-containing target type of this bean.
164+
* @see #targetType(ParameterizedTypeReference)
165+
* @see RootBeanDefinition#setTargetType(ResolvableType)
166+
*/
167+
Spec<T> targetType(ResolvableType type);
152168
}
153169

154170
/**

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

+13
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.beans.factory.ObjectProvider;
3131
import org.springframework.beans.factory.config.BeanDefinition;
3232
import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
33+
import org.springframework.core.ParameterizedTypeReference;
3334
import org.springframework.core.ResolvableType;
3435
import org.springframework.util.MultiValueMap;
3536

@@ -211,6 +212,18 @@ public Spec<T> supplier(Function<SupplierContext, T> supplier) {
211212
supplier.apply(new SupplierContextAdapter(this.beanFactory)));
212213
return this;
213214
}
215+
216+
@Override
217+
public Spec<T> targetType(ParameterizedTypeReference<? extends T> targetType) {
218+
this.beanDefinition.setTargetType(ResolvableType.forType(targetType));
219+
return this;
220+
}
221+
222+
@Override
223+
public Spec<T> targetType(ResolvableType targetType) {
224+
this.beanDefinition.setTargetType(targetType);
225+
return this;
226+
}
214227
}
215228

216229
static class SupplierContextAdapter implements SupplierContext {

spring-beans/src/main/kotlin/org/springframework/beans/factory/BeanRegistrarDsl.kt

+16
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
122122
if (prototype) {
123123
it.prototype()
124124
}
125+
val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference<T>() {});
126+
if (resolvableType.hasGenerics()) {
127+
it.targetType(resolvableType)
128+
}
125129
}
126130
registry.registerBean(name, T::class.java, customizer)
127131
}
@@ -184,6 +188,10 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
184188
if (prototype) {
185189
it.prototype()
186190
}
191+
val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference<T>() {});
192+
if (resolvableType.hasGenerics()) {
193+
it.targetType(resolvableType)
194+
}
187195
}
188196
return registry.registerBean(T::class.java, customizer)
189197
}
@@ -250,6 +258,10 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
250258
it.supplier {
251259
SupplierContextDsl<T>(it).supplier()
252260
}
261+
val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference<T>() {});
262+
if (resolvableType.hasGenerics()) {
263+
it.targetType(resolvableType)
264+
}
253265
}
254266
registry.registerBean(name, T::class.java, customizer)
255267
}
@@ -314,6 +326,10 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
314326
it.supplier {
315327
SupplierContextDsl<T>(it).supplier()
316328
}
329+
val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference<T>() {});
330+
if (resolvableType.hasGenerics()) {
331+
it.targetType(resolvableType)
332+
}
317333
}
318334
return registry.registerBean(T::class.java, customizer)
319335
}

spring-beans/src/test/java/org/springframework/beans/factory/support/BeanRegistryAdapterTests.java

+30
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.springframework.beans.factory.BeanRegistrar;
2424
import org.springframework.beans.factory.BeanRegistry;
2525
import org.springframework.beans.factory.config.BeanDefinition;
26+
import org.springframework.core.ParameterizedTypeReference;
27+
import org.springframework.core.ResolvableType;
2628
import org.springframework.core.env.Environment;
2729
import org.springframework.core.env.StandardEnvironment;
2830

@@ -203,6 +205,22 @@ void customSupplier() {
203205
assertThat(supplier.get()).isNotNull().isInstanceOf(Foo.class);
204206
}
205207

208+
@Test
209+
void customTargetTypeFromResolvableType() {
210+
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, TargetTypeBeanRegistrar.class);
211+
new TargetTypeBeanRegistrar().register(adapter, env);
212+
RootBeanDefinition beanDefinition = (RootBeanDefinition)this.beanFactory.getBeanDefinition("fooSupplierFromResolvableType");
213+
assertThat(beanDefinition.getResolvableType().resolveGeneric(0)).isEqualTo(Foo.class);
214+
}
215+
216+
@Test
217+
void customTargetTypeFromTypeReference() {
218+
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, TargetTypeBeanRegistrar.class);
219+
new TargetTypeBeanRegistrar().register(adapter, env);
220+
RootBeanDefinition beanDefinition = (RootBeanDefinition)this.beanFactory.getBeanDefinition("fooSupplierFromTypeReference");
221+
assertThat(beanDefinition.getResolvableType().resolveGeneric(0)).isEqualTo(Foo.class);
222+
}
223+
206224

207225
private static class DefaultBeanRegistrar implements BeanRegistrar {
208226

@@ -292,6 +310,18 @@ public void register(BeanRegistry registry, Environment env) {
292310
}
293311
}
294312

313+
private static class TargetTypeBeanRegistrar implements BeanRegistrar {
314+
315+
@Override
316+
public void register(BeanRegistry registry, Environment env) {
317+
registry.registerBean("fooSupplierFromResolvableType", Foo.class,
318+
spec -> spec.targetType(ResolvableType.forClassWithGenerics(Supplier.class, Foo.class)));
319+
ParameterizedTypeReference<Supplier<Foo>> type = new ParameterizedTypeReference<>() {};
320+
registry.registerBean("fooSupplierFromTypeReference", Supplier.class,
321+
spec -> spec.targetType(type));
322+
}
323+
}
324+
295325
private static class Foo {}
296326

297327
}

spring-context/src/test/java/org/springframework/context/annotation/beanregistrar/BeanRegistrarConfigurationTests.java

+12
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@
2121
import org.springframework.beans.factory.BeanRegistrar;
2222
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2323
import org.springframework.beans.factory.config.BeanDefinition;
24+
import org.springframework.beans.factory.support.RootBeanDefinition;
2425
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
26+
import org.springframework.context.testfixture.beans.factory.GenericBeanRegistrar;
2527
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Bar;
2628
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Baz;
2729
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Foo;
2830
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Init;
2931
import org.springframework.context.testfixture.context.annotation.registrar.BeanRegistrarConfiguration;
32+
import org.springframework.context.testfixture.context.annotation.registrar.GenericBeanRegistrarConfiguration;
3033

3134
import static org.assertj.core.api.Assertions.assertThat;
3235
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -69,4 +72,13 @@ void scannedFunctionalConfiguration() {
6972
assertThat(context.getBean(Init.class).initialized).isTrue();
7073
}
7174

75+
@Test
76+
void beanRegistrarWithTargetType() {
77+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
78+
context.register(GenericBeanRegistrarConfiguration.class);
79+
context.refresh();
80+
RootBeanDefinition beanDefinition = (RootBeanDefinition)context.getBeanDefinition("fooSupplier");
81+
assertThat(beanDefinition.getResolvableType().resolveGeneric(0)).isEqualTo(GenericBeanRegistrar.Foo.class);
82+
}
83+
7284
}

spring-context/src/test/kotlin/org/springframework/context/annotation/BeanRegistrarDslConfigurationTests.kt

+24-1
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import org.assertj.core.api.Assertions.assertThat
2020
import org.assertj.core.api.Assertions.assertThatThrownBy
2121
import org.assertj.core.api.ThrowableAssert
2222
import org.junit.jupiter.api.Test
23+
import org.springframework.beans.factory.BeanRegistrarDsl
2324
import org.springframework.beans.factory.InitializingBean
2425
import org.springframework.beans.factory.NoSuchBeanDefinitionException
2526
import org.springframework.beans.factory.config.BeanDefinition
2627
import org.springframework.beans.factory.getBean
27-
import org.springframework.beans.factory.BeanRegistrarDsl
28+
import org.springframework.beans.factory.support.RootBeanDefinition
29+
import java.util.function.Supplier
2830

2931
/**
3032
* Kotlin tests leveraging [BeanRegistrarDsl].
@@ -56,6 +58,13 @@ class BeanRegistrarDslConfigurationTests {
5658
assertThat(context.getBean<Init>().initialized).isTrue()
5759
}
5860

61+
@Test
62+
fun genericBeanRegistrar() {
63+
val context = AnnotationConfigApplicationContext(GenericBeanRegistrarKotlinConfiguration::class.java)
64+
val beanDefinition = context.getBeanDefinition("fooSupplier") as RootBeanDefinition
65+
assertThat(beanDefinition.resolvableType.resolveGeneric(0)).isEqualTo(Foo::class.java)
66+
}
67+
5968
class Foo
6069
data class Bar(val foo: Foo)
6170
data class Baz(val message: String = "")
@@ -86,4 +95,18 @@ class BeanRegistrarDslConfigurationTests {
8695
}
8796
registerBean<Init>()
8897
})
98+
99+
@Configuration
100+
@Import(GenericBeanRegistrar::class)
101+
internal class GenericBeanRegistrarKotlinConfiguration
102+
103+
private class GenericBeanRegistrar : BeanRegistrarDsl({
104+
registerBean<Supplier<Foo>>(name = "fooSupplier") {
105+
object: Supplier<Foo> {
106+
override fun get(): Foo {
107+
return Foo()
108+
}
109+
}
110+
}
111+
})
89112
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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.context.testfixture.beans.factory;
18+
19+
import java.util.function.Supplier;
20+
21+
import org.springframework.beans.factory.BeanRegistrar;
22+
import org.springframework.beans.factory.BeanRegistry;
23+
import org.springframework.core.ParameterizedTypeReference;
24+
import org.springframework.core.env.Environment;
25+
26+
public class GenericBeanRegistrar implements BeanRegistrar {
27+
28+
@Override
29+
public void register(BeanRegistry registry, Environment env) {
30+
ParameterizedTypeReference<Supplier<Foo>> type = new ParameterizedTypeReference<>() {};
31+
registry.registerBean("fooSupplier", Supplier.class, spec -> spec.targetType(type)
32+
.supplier(context-> (Supplier<Foo>) Foo::new));
33+
}
34+
35+
public record Foo() {}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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.context.testfixture.context.annotation.registrar;
18+
19+
import org.springframework.context.annotation.Configuration;
20+
import org.springframework.context.annotation.Import;
21+
import org.springframework.context.testfixture.beans.factory.GenericBeanRegistrar;
22+
23+
@Configuration
24+
@Import(GenericBeanRegistrar.class)
25+
public class GenericBeanRegistrarConfiguration {
26+
}

0 commit comments

Comments
 (0)