From a0e2d3a22144557ed6799c39ae48322f37047d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= <141109+sdeleuze@users.noreply.github.com> Date: Mon, 10 Mar 2025 10:14:28 +0100 Subject: [PATCH] Add support for target type to BeanRegistry Closes gh-34560 --- .../beans/factory/BeanRegistry.java | 16 +++++++++ .../factory/support/BeanRegistryAdapter.java | 13 +++++++ .../beans/factory/BeanRegistrarDsl.kt | 16 +++++++++ .../support/BeanRegistryAdapterTests.java | 30 ++++++++++++++++ .../BeanRegistrarConfigurationTests.java | 12 +++++++ .../BeanRegistrarDslConfigurationTests.kt | 25 ++++++++++++- .../beans/factory/GenericBeanRegistrar.java | 36 +++++++++++++++++++ .../GenericBeanRegistrarConfiguration.java | 26 ++++++++++++++ 8 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/GenericBeanRegistrar.java create mode 100644 spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/registrar/GenericBeanRegistrarConfiguration.java 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 index 26cbc577acd..2b10257bf99 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistry.java @@ -24,6 +24,8 @@ 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.beans.factory.support.RootBeanDefinition; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ResolvableType; import org.springframework.core.env.Environment; @@ -149,6 +151,20 @@ public interface BeanRegistry { * @see AbstractBeanDefinition#setInstanceSupplier(Supplier) */ Spec supplier(Function supplier); + + /** + * Set a generics-containing target type of this bean. + * @see #targetType(ResolvableType) + * @see RootBeanDefinition#setTargetType(ResolvableType) + */ + Spec targetType(ParameterizedTypeReference type); + + /** + * Set a generics-containing target type of this bean. + * @see #targetType(ParameterizedTypeReference) + * @see RootBeanDefinition#setTargetType(ResolvableType) + */ + Spec targetType(ResolvableType type); } /** 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 dfc0c0aac98..fd100df49d8 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 @@ -30,6 +30,7 @@ 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.ParameterizedTypeReference; import org.springframework.core.ResolvableType; import org.springframework.util.MultiValueMap; @@ -211,6 +212,18 @@ public class BeanRegistryAdapter implements BeanRegistry { supplier.apply(new SupplierContextAdapter(this.beanFactory))); return this; } + + @Override + public Spec targetType(ParameterizedTypeReference targetType) { + this.beanDefinition.setTargetType(ResolvableType.forType(targetType)); + return this; + } + + @Override + public Spec targetType(ResolvableType targetType) { + this.beanDefinition.setTargetType(targetType); + return this; + } } static class SupplierContextAdapter implements SupplierContext { diff --git a/spring-beans/src/main/kotlin/org/springframework/beans/factory/BeanRegistrarDsl.kt b/spring-beans/src/main/kotlin/org/springframework/beans/factory/BeanRegistrarDsl.kt index 799190319ee..a28328dd2a6 100644 --- a/spring-beans/src/main/kotlin/org/springframework/beans/factory/BeanRegistrarDsl.kt +++ b/spring-beans/src/main/kotlin/org/springframework/beans/factory/BeanRegistrarDsl.kt @@ -122,6 +122,10 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean if (prototype) { it.prototype() } + val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference() {}); + if (resolvableType.hasGenerics()) { + it.targetType(resolvableType) + } } registry.registerBean(name, T::class.java, customizer) } @@ -184,6 +188,10 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean if (prototype) { it.prototype() } + val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference() {}); + if (resolvableType.hasGenerics()) { + it.targetType(resolvableType) + } } return registry.registerBean(T::class.java, customizer) } @@ -250,6 +258,10 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean it.supplier { SupplierContextDsl(it).supplier() } + val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference() {}); + if (resolvableType.hasGenerics()) { + it.targetType(resolvableType) + } } registry.registerBean(name, T::class.java, customizer) } @@ -314,6 +326,10 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean it.supplier { SupplierContextDsl(it).supplier() } + val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference() {}); + if (resolvableType.hasGenerics()) { + it.targetType(resolvableType) + } } return registry.registerBean(T::class.java, customizer) } 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 index 81e0c3840e9..5b3f632a4ee 100644 --- 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 @@ -23,6 +23,8 @@ 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.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.core.env.Environment; import org.springframework.core.env.StandardEnvironment; @@ -203,6 +205,22 @@ public class BeanRegistryAdapterTests { assertThat(supplier.get()).isNotNull().isInstanceOf(Foo.class); } + @Test + void customTargetTypeFromResolvableType() { + BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, TargetTypeBeanRegistrar.class); + new TargetTypeBeanRegistrar().register(adapter, env); + RootBeanDefinition beanDefinition = (RootBeanDefinition)this.beanFactory.getBeanDefinition("fooSupplierFromResolvableType"); + assertThat(beanDefinition.getResolvableType().resolveGeneric(0)).isEqualTo(Foo.class); + } + + @Test + void customTargetTypeFromTypeReference() { + BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, TargetTypeBeanRegistrar.class); + new TargetTypeBeanRegistrar().register(adapter, env); + RootBeanDefinition beanDefinition = (RootBeanDefinition)this.beanFactory.getBeanDefinition("fooSupplierFromTypeReference"); + assertThat(beanDefinition.getResolvableType().resolveGeneric(0)).isEqualTo(Foo.class); + } + private static class DefaultBeanRegistrar implements BeanRegistrar { @@ -292,6 +310,18 @@ public class BeanRegistryAdapterTests { } } + private static class TargetTypeBeanRegistrar implements BeanRegistrar { + + @Override + public void register(BeanRegistry registry, Environment env) { + registry.registerBean("fooSupplierFromResolvableType", Foo.class, + spec -> spec.targetType(ResolvableType.forClassWithGenerics(Supplier.class, Foo.class))); + ParameterizedTypeReference> type = new ParameterizedTypeReference<>() {}; + registry.registerBean("fooSupplierFromTypeReference", Supplier.class, + spec -> spec.targetType(type)); + } + } + private static class Foo {} } 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 index 5cbbb4e44c3..d002c4ef6de 100644 --- 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 @@ -21,12 +21,15 @@ 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.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.testfixture.beans.factory.GenericBeanRegistrar; 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 org.springframework.context.testfixture.context.annotation.registrar.GenericBeanRegistrarConfiguration; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -69,4 +72,13 @@ public class BeanRegistrarConfigurationTests { assertThat(context.getBean(Init.class).initialized).isTrue(); } + @Test + void beanRegistrarWithTargetType() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(GenericBeanRegistrarConfiguration.class); + context.refresh(); + RootBeanDefinition beanDefinition = (RootBeanDefinition)context.getBeanDefinition("fooSupplier"); + assertThat(beanDefinition.getResolvableType().resolveGeneric(0)).isEqualTo(GenericBeanRegistrar.Foo.class); + } + } diff --git a/spring-context/src/test/kotlin/org/springframework/context/annotation/BeanRegistrarDslConfigurationTests.kt b/spring-context/src/test/kotlin/org/springframework/context/annotation/BeanRegistrarDslConfigurationTests.kt index 6bbc0ee3ff3..1ae9bd234b7 100644 --- a/spring-context/src/test/kotlin/org/springframework/context/annotation/BeanRegistrarDslConfigurationTests.kt +++ b/spring-context/src/test/kotlin/org/springframework/context/annotation/BeanRegistrarDslConfigurationTests.kt @@ -20,11 +20,13 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.ThrowableAssert import org.junit.jupiter.api.Test +import org.springframework.beans.factory.BeanRegistrarDsl import org.springframework.beans.factory.InitializingBean import org.springframework.beans.factory.NoSuchBeanDefinitionException import org.springframework.beans.factory.config.BeanDefinition import org.springframework.beans.factory.getBean -import org.springframework.beans.factory.BeanRegistrarDsl +import org.springframework.beans.factory.support.RootBeanDefinition +import java.util.function.Supplier /** * Kotlin tests leveraging [BeanRegistrarDsl]. @@ -56,6 +58,13 @@ class BeanRegistrarDslConfigurationTests { assertThat(context.getBean().initialized).isTrue() } + @Test + fun genericBeanRegistrar() { + val context = AnnotationConfigApplicationContext(GenericBeanRegistrarKotlinConfiguration::class.java) + val beanDefinition = context.getBeanDefinition("fooSupplier") as RootBeanDefinition + assertThat(beanDefinition.resolvableType.resolveGeneric(0)).isEqualTo(Foo::class.java) + } + class Foo data class Bar(val foo: Foo) data class Baz(val message: String = "") @@ -86,4 +95,18 @@ class BeanRegistrarDslConfigurationTests { } registerBean() }) + + @Configuration + @Import(GenericBeanRegistrar::class) + internal class GenericBeanRegistrarKotlinConfiguration + + private class GenericBeanRegistrar : BeanRegistrarDsl({ + registerBean>(name = "fooSupplier") { + object: Supplier { + override fun get(): Foo { + return Foo() + } + } + } + }) } diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/GenericBeanRegistrar.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/GenericBeanRegistrar.java new file mode 100644 index 00000000000..c2d61d2b48f --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/GenericBeanRegistrar.java @@ -0,0 +1,36 @@ +/* + * 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 java.util.function.Supplier; + +import org.springframework.beans.factory.BeanRegistrar; +import org.springframework.beans.factory.BeanRegistry; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.env.Environment; + +public class GenericBeanRegistrar implements BeanRegistrar { + + @Override + public void register(BeanRegistry registry, Environment env) { + ParameterizedTypeReference> type = new ParameterizedTypeReference<>() {}; + registry.registerBean("fooSupplier", Supplier.class, spec -> spec.targetType(type) + .supplier(context-> (Supplier) Foo::new)); + } + + public record Foo() {} +} diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/registrar/GenericBeanRegistrarConfiguration.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/registrar/GenericBeanRegistrarConfiguration.java new file mode 100644 index 00000000000..69e21168608 --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/registrar/GenericBeanRegistrarConfiguration.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.GenericBeanRegistrar; + +@Configuration +@Import(GenericBeanRegistrar.class) +public class GenericBeanRegistrarConfiguration { +}