From b46c41bb3ae18dbb64388f9bc6e00e21733f5098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Wed, 15 Oct 2025 15:07:50 +0200 Subject: [PATCH] Add first class ParameterizedTypeReference support to BeanRegistrar This commit replaces ParameterizedTypeReference and ResolvableType target type customization with the lambda by directly exposing ParameterizedTypeReference methods at top level, as generics variants of the class-based existing ones. Closes gh-35635 --- .../beans/factory/BeanRegistry.java | 106 ++++++++++++------ .../factory/support/BeanRegistryAdapter.java | 84 ++++++++++---- .../beans/factory/BeanRegistrarDsl.kt | 26 +---- .../support/BeanRegistryAdapterTests.java | 25 +---- .../beans/factory/GenericBeanRegistrar.java | 5 +- 5 files changed, 149 insertions(+), 97 deletions(-) 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 a9beddaf545..3348da86fd8 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,7 +24,6 @@ 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; @@ -55,43 +54,92 @@ public interface BeanRegistry { void registerAlias(String name, String alias); /** - * Register a bean from the given bean class, which will be instantiated using the + * Register a bean from the given class, which will be instantiated using the * related {@link BeanUtils#getResolvableConstructor resolvable constructor} if any. + *

For registering a bean with a generic type, consider + * {@link #registerBean(ParameterizedTypeReference)}. * @param beanClass the class of the bean * @return the generated bean name + * @see #registerBean(Class) */ String registerBean(Class beanClass); /** - * Register a bean from the given bean class, customizing it with the customizer + * Register a bean from the given generics-containing type, which will be + * instantiated using the related + * {@link BeanUtils#getResolvableConstructor resolvable constructor} if any. + * @param beanType the generics-containing type of the bean + * @return the generated bean name + */ + String registerBean(ParameterizedTypeReference beanType); + + /** + * Register a bean from the given class, customizing it with the customizer * callback. The bean will be instantiated using the supplier that can be configured * in the customizer callback, or will be tentatively instantiated with its * {@link BeanUtils#getResolvableConstructor resolvable constructor} otherwise. + *

For registering a bean with a generic type, consider + * {@link #registerBean(ParameterizedTypeReference, Consumer)}. * @param beanClass the class of the bean - * @param customizer callback to customize other bean properties than the name + * @param customizer the callback to customize other bean properties than the name * @return the generated bean name */ String registerBean(Class beanClass, Consumer> customizer); /** - * Register a bean from the given bean class, which will be instantiated using the + * Register a bean from the given generics-containing type, customizing it + * with the customizer callback. The bean will be instantiated using the supplier + * that can be configured in the customizer callback, or will be tentatively instantiated + * with its {@link BeanUtils#getResolvableConstructor resolvable constructor} otherwise. + * @param beanType the generics-containing type of the bean + * @param customizer the callback to customize other bean properties than the name + * @return the generated bean name + */ + String registerBean(ParameterizedTypeReference beanType, Consumer> customizer); + + /** + * Register a bean from the given class, which will be instantiated using the * related {@link BeanUtils#getResolvableConstructor resolvable constructor} if any. + *

For registering a bean with a generic type, consider + * {@link #registerBean(String, ParameterizedTypeReference)}. * @param name the name of the bean * @param beanClass the class of the bean */ void registerBean(String name, Class beanClass); /** - * Register a bean from the given bean class, customizing it with the customizer + * Register a bean from the given generics-containing type, which + * will be instantiated using the related + * {@link BeanUtils#getResolvableConstructor resolvable constructor} if any. + * @param name the name of the bean + * @param beanType the generics-containing type of the bean + */ + void registerBean(String name, ParameterizedTypeReference beanType); + + /** + * Register a bean from the given class, customizing it with the customizer * callback. The bean will be instantiated using the supplier that can be configured * in the customizer callback, or will be tentatively instantiated with its * {@link BeanUtils#getResolvableConstructor resolvable constructor} otherwise. + *

For registering a bean with a generic type, consider + * {@link #registerBean(String, ParameterizedTypeReference, Consumer)}. * @param name the name of the bean * @param beanClass the class of the bean - * @param customizer callback to customize other bean properties than the name + * @param customizer the callback to customize other bean properties than the name */ void registerBean(String name, Class beanClass, Consumer> customizer); + /** + * Register a bean from the given generics-containing type, customizing it + * with the customizer callback. The bean will be instantiated using the supplier + * that can be configured in the customizer callback, or will be tentatively instantiated + * with its {@link BeanUtils#getResolvableConstructor resolvable constructor} otherwise. + * @param name the name of the bean + * @param beanType the generics-containing type of the bean + * @param customizer the callback to customize other bean properties than the name + */ + void registerBean(String name, ParameterizedTypeReference beanType, Consumer> customizer); + /** * Specification for customizing a bean. @@ -164,20 +212,6 @@ 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); } @@ -188,32 +222,40 @@ public interface BeanRegistry { interface SupplierContext { /** - * Return the bean instance that uniquely matches the given object type, if any. - * @param requiredType type the bean must match; can be an interface or superclass - * @return an instance of the single bean matching the required type + * Return the bean instance that uniquely matches the given type, if any. + * @param beanClass the type the bean must match; can be an interface or superclass + * @return an instance of the single bean matching the bean type + * @see BeanFactory#getBean(String) + */ + T bean(Class beanClass) throws BeansException; + + /** + * Return the bean instance that uniquely matches the given generics-containing type, if any. + * @param beanType the generics-containing type the bean must match; can be an interface or superclass + * @return an instance of the single bean matching the bean type * @see BeanFactory#getBean(String) */ - T bean(Class requiredType) throws BeansException; + T bean(ParameterizedTypeReference beanType) throws BeansException; /** * Return an instance, which may be shared or independent, of the * specified bean. * @param name the name of the bean to retrieve - * @param requiredType type the bean must match; can be an interface or superclass + * @param beanClass the type the bean must match; can be an interface or superclass * @return an instance of the bean. * @see BeanFactory#getBean(String, Class) */ - T bean(String name, Class requiredType) throws BeansException; + T bean(String name, Class beanClass) throws BeansException; /** * Return a provider for the specified bean, allowing for lazy on-demand retrieval * of instances, including availability and uniqueness options. - *

For matching a generic type, consider {@link #beanProvider(ResolvableType)}. - * @param requiredType type the bean must match; can be an interface or superclass + *

For matching a generic type, consider {@link #beanProvider(ParameterizedTypeReference)}. + * @param beanClass the type the bean must match; can be an interface or superclass * @return a corresponding provider handle * @see BeanFactory#getBeanProvider(Class) */ - ObjectProvider beanProvider(Class requiredType); + ObjectProvider beanProvider(Class beanClass); /** * Return a provider for the specified bean, allowing for lazy on-demand retrieval @@ -229,11 +271,11 @@ public interface BeanRegistry { * Java compiler warning), consider calling {@link #beanProvider(Class)} with the * raw type as a second step if no full generic match is * {@link ObjectProvider#getIfAvailable() available} with this variant. - * @param requiredType type the bean must match; can be a generic type declaration + * @param beanType the generics-containing type the bean must match; can be an interface or superclass * @return a corresponding provider handle * @see BeanFactory#getBeanProvider(ResolvableType) */ - ObjectProvider beanProvider(ResolvableType requiredType); + ObjectProvider beanProvider(ParameterizedTypeReference beanType); } } 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 9131786e531..103c59dd2f5 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 @@ -17,6 +17,7 @@ package org.springframework.beans.factory.support; import java.lang.reflect.Constructor; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; @@ -92,6 +93,14 @@ public class BeanRegistryAdapter implements BeanRegistry { return beanName; } + @Override + public String registerBean(ParameterizedTypeReference beanType) { + ResolvableType resolvableType = ResolvableType.forType(beanType); + String beanName = BeanDefinitionReaderUtils.uniqueBeanName(Objects.requireNonNull(resolvableType.resolve()).getName(), this.beanRegistry); + registerBean(beanName, beanType); + return beanName; + } + @Override public String registerBean(Class beanClass, Consumer> customizer) { String beanName = BeanDefinitionReaderUtils.uniqueBeanName(beanClass.getName(), this.beanRegistry); @@ -99,6 +108,15 @@ public class BeanRegistryAdapter implements BeanRegistry { return beanName; } + @Override + public String registerBean(ParameterizedTypeReference beanType, Consumer> customizer) { + ResolvableType resolvableType = ResolvableType.forType(beanType); + Class beanClass = Objects.requireNonNull(resolvableType.resolve()); + String beanName = BeanDefinitionReaderUtils.uniqueBeanName(beanClass.getName(), this.beanRegistry); + registerBean(beanName, beanType, customizer); + return beanName; + } + @Override public void registerBean(String name, Class beanClass) { BeanRegistrarBeanDefinition beanDefinition = new BeanRegistrarBeanDefinition(beanClass, this.beanRegistrarClass); @@ -111,9 +129,11 @@ public class BeanRegistryAdapter implements BeanRegistry { } @Override - public void registerBean(String name, Class beanClass, Consumer> spec) { + public void registerBean(String name, ParameterizedTypeReference beanType) { + ResolvableType resolvableType = ResolvableType.forType(beanType); + Class beanClass = Objects.requireNonNull(resolvableType.resolve()); BeanRegistrarBeanDefinition beanDefinition = new BeanRegistrarBeanDefinition(beanClass, this.beanRegistrarClass); - spec.accept(new BeanSpecAdapter<>(beanDefinition, this.beanFactory)); + beanDefinition.setTargetType(resolvableType); if (this.customizers != null && this.customizers.containsKey(name)) { for (BeanDefinitionCustomizer customizer : this.customizers.get(name)) { customizer.customize(beanDefinition); @@ -122,6 +142,33 @@ public class BeanRegistryAdapter implements BeanRegistry { this.beanRegistry.registerBeanDefinition(name, beanDefinition); } + @Override + public void registerBean(String name, Class beanClass, Consumer> customizer) { + BeanRegistrarBeanDefinition beanDefinition = new BeanRegistrarBeanDefinition(beanClass, this.beanRegistrarClass); + customizer.accept(new BeanSpecAdapter<>(beanDefinition, this.beanFactory)); + if (this.customizers != null && this.customizers.containsKey(name)) { + for (BeanDefinitionCustomizer registryCustomizer : this.customizers.get(name)) { + registryCustomizer.customize(beanDefinition); + } + } + this.beanRegistry.registerBeanDefinition(name, beanDefinition); + } + + @Override + public void registerBean(String name, ParameterizedTypeReference beanType, Consumer> customizer) { + ResolvableType resolvableType = ResolvableType.forType(beanType); + Class beanClass = Objects.requireNonNull(resolvableType.resolve()); + BeanRegistrarBeanDefinition beanDefinition = new BeanRegistrarBeanDefinition(beanClass, this.beanRegistrarClass); + beanDefinition.setTargetType(resolvableType); + customizer.accept(new BeanSpecAdapter<>(beanDefinition, this.beanFactory)); + if (this.customizers != null && this.customizers.containsKey(name)) { + for (BeanDefinitionCustomizer registryCustomizer : this.customizers.get(name)) { + registryCustomizer.customize(beanDefinition); + } + } + this.beanRegistry.registerBeanDefinition(name, beanDefinition); + } + @Override public void register(BeanRegistrar registrar) { Assert.notNull(registrar, "'registrar' must not be null"); @@ -238,18 +285,6 @@ 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; - } } @@ -262,23 +297,28 @@ public class BeanRegistryAdapter implements BeanRegistry { } @Override - public T bean(Class requiredType) throws BeansException { - return this.beanFactory.getBean(requiredType); + public T bean(Class beanClass) throws BeansException { + return this.beanFactory.getBean(beanClass); + } + + @Override + public T bean(ParameterizedTypeReference beanType) throws BeansException { + return this.beanFactory.getBeanProvider(beanType).getObject(); } @Override - public T bean(String name, Class requiredType) throws BeansException { - return this.beanFactory.getBean(name, requiredType); + public T bean(String name, Class beanClass) throws BeansException { + return this.beanFactory.getBean(name, beanClass); } @Override - public ObjectProvider beanProvider(Class requiredType) { - return this.beanFactory.getBeanProvider(requiredType); + public ObjectProvider beanProvider(Class beanClass) { + return this.beanFactory.getBeanProvider(beanClass); } @Override - public ObjectProvider beanProvider(ResolvableType requiredType) { - return this.beanFactory.getBeanProvider(requiredType); + public ObjectProvider beanProvider(ParameterizedTypeReference beanType) { + return this.beanFactory.getBeanProvider(beanType); } } 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 a6a4a56a64f..7deae838654 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 @@ -168,12 +168,8 @@ 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) + registry.registerBean(name, object: ParameterizedTypeReference() {}, customizer) } /** @@ -234,12 +230,8 @@ 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) + return registry.registerBean(object: ParameterizedTypeReference() {}, customizer) } /** @@ -304,12 +296,8 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean it.supplier { SupplierContextDsl(it, env).supplier() } - val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference() {}); - if (resolvableType.hasGenerics()) { - it.targetType(resolvableType) - } } - registry.registerBean(name, T::class.java, customizer) + registry.registerBean(name, object: ParameterizedTypeReference() {}, customizer) } inline fun registerBean(autowirable: Boolean = true, @@ -372,12 +360,8 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean it.supplier { SupplierContextDsl(it, env).supplier() } - val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference() {}); - if (resolvableType.hasGenerics()) { - it.targetType(resolvableType) - } } - return registry.registerBean(T::class.java, customizer) + return registry.registerBean(object: ParameterizedTypeReference() {}, customizer) } // Function with 0 parameter @@ -1094,7 +1078,7 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean * @return a corresponding provider handle */ inline fun beanProvider() : ObjectProvider = - context.beanProvider(ResolvableType.forType((object : ParameterizedTypeReference() {}).type)) + context.beanProvider(object : ParameterizedTypeReference() {}) } override fun register(registry: BeanRegistry, env: Environment) { 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 51461235a88..c1ceba10796 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 @@ -24,7 +24,6 @@ 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; @@ -206,18 +205,10 @@ public class BeanRegistryAdapterTests { } @Test - void customTargetTypeFromResolvableType() { - BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, env, 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, env, TargetTypeBeanRegistrar.class); - new TargetTypeBeanRegistrar().register(adapter, env); - RootBeanDefinition beanDefinition = (RootBeanDefinition)this.beanFactory.getBeanDefinition("fooSupplierFromTypeReference"); + void genericType() { + BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, env, GenericTypeBeanRegistrar.class); + new GenericTypeBeanRegistrar().register(adapter, env); + RootBeanDefinition beanDefinition = (RootBeanDefinition)this.beanFactory.getBeanDefinition("fooSupplier"); assertThat(beanDefinition.getResolvableType().resolveGeneric(0)).isEqualTo(Foo.class); } @@ -325,15 +316,11 @@ public class BeanRegistryAdapterTests { } } - private static class TargetTypeBeanRegistrar implements BeanRegistrar { + private static class GenericTypeBeanRegistrar 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)); + registry.registerBean("fooSupplier", new ParameterizedTypeReference>() {}); } } 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 index 4f1801d3034..b732d9a2179 100644 --- 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 @@ -27,9 +27,8 @@ 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)); + registry.registerBean("fooSupplier", new ParameterizedTypeReference>() {}, spec -> + spec.supplier(context-> (Supplier) Foo::new)); } public record Foo() {}