Browse Source

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
pull/35649/head
Sébastien Deleuze 2 months ago
parent
commit
b46c41bb3a
  1. 106
      spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistry.java
  2. 84
      spring-beans/src/main/java/org/springframework/beans/factory/support/BeanRegistryAdapter.java
  3. 26
      spring-beans/src/main/kotlin/org/springframework/beans/factory/BeanRegistrarDsl.kt
  4. 25
      spring-beans/src/test/java/org/springframework/beans/factory/support/BeanRegistryAdapterTests.java
  5. 5
      spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/GenericBeanRegistrar.java

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

@ -24,7 +24,6 @@ import org.springframework.beans.BeanUtils; @@ -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 { @@ -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.
* <p>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)
*/
<T> String registerBean(Class<T> 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
*/
<T> String registerBean(ParameterizedTypeReference<T> 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.
* <p>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
*/
<T> String registerBean(Class<T> beanClass, Consumer<Spec<T>> 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
*/
<T> String registerBean(ParameterizedTypeReference<T> beanType, Consumer<Spec<T>> customizer);
/**
* Register a bean from the given class, which will be instantiated using the
* related {@link BeanUtils#getResolvableConstructor resolvable constructor} if any.
* <p>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
*/
<T> void registerBean(String name, Class<T> 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
*/
<T> void registerBean(String name, ParameterizedTypeReference<T> 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.
* <p>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
*/
<T> void registerBean(String name, Class<T> beanClass, Consumer<Spec<T>> 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
*/
<T> void registerBean(String name, ParameterizedTypeReference<T> beanType, Consumer<Spec<T>> customizer);
/**
* Specification for customizing a bean.
@ -164,20 +212,6 @@ public interface BeanRegistry { @@ -164,20 +212,6 @@ public interface BeanRegistry {
* @see AbstractBeanDefinition#setInstanceSupplier(Supplier)
*/
Spec<T> supplier(Function<SupplierContext, T> supplier);
/**
* Set a generics-containing target type of this bean.
* @see #targetType(ResolvableType)
* @see RootBeanDefinition#setTargetType(ResolvableType)
*/
Spec<T> targetType(ParameterizedTypeReference<? extends T> type);
/**
* Set a generics-containing target type of this bean.
* @see #targetType(ParameterizedTypeReference)
* @see RootBeanDefinition#setTargetType(ResolvableType)
*/
Spec<T> targetType(ResolvableType type);
}
@ -188,32 +222,40 @@ public interface BeanRegistry { @@ -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> T bean(Class<T> 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> T bean(Class<T> requiredType) throws BeansException;
<T> T bean(ParameterizedTypeReference<T> 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> T bean(String name, Class<T> requiredType) throws BeansException;
<T> T bean(String name, Class<T> beanClass) throws BeansException;
/**
* Return a provider for the specified bean, allowing for lazy on-demand retrieval
* of instances, including availability and uniqueness options.
* <p>For matching a generic type, consider {@link #beanProvider(ResolvableType)}.
* @param requiredType type the bean must match; can be an interface or superclass
* <p>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)
*/
<T> ObjectProvider<T> beanProvider(Class<T> requiredType);
<T> ObjectProvider<T> beanProvider(Class<T> beanClass);
/**
* Return a provider for the specified bean, allowing for lazy on-demand retrieval
@ -229,11 +271,11 @@ public interface BeanRegistry { @@ -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)
*/
<T> ObjectProvider<T> beanProvider(ResolvableType requiredType);
<T> ObjectProvider<T> beanProvider(ParameterizedTypeReference<T> beanType);
}
}

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

@ -17,6 +17,7 @@ @@ -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 { @@ -92,6 +93,14 @@ public class BeanRegistryAdapter implements BeanRegistry {
return beanName;
}
@Override
public <T> String registerBean(ParameterizedTypeReference<T> beanType) {
ResolvableType resolvableType = ResolvableType.forType(beanType);
String beanName = BeanDefinitionReaderUtils.uniqueBeanName(Objects.requireNonNull(resolvableType.resolve()).getName(), this.beanRegistry);
registerBean(beanName, beanType);
return beanName;
}
@Override
public <T> String registerBean(Class<T> beanClass, Consumer<Spec<T>> customizer) {
String beanName = BeanDefinitionReaderUtils.uniqueBeanName(beanClass.getName(), this.beanRegistry);
@ -99,6 +108,15 @@ public class BeanRegistryAdapter implements BeanRegistry { @@ -99,6 +108,15 @@ public class BeanRegistryAdapter implements BeanRegistry {
return beanName;
}
@Override
public <T> String registerBean(ParameterizedTypeReference<T> beanType, Consumer<Spec<T>> 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 <T> void registerBean(String name, Class<T> beanClass) {
BeanRegistrarBeanDefinition beanDefinition = new BeanRegistrarBeanDefinition(beanClass, this.beanRegistrarClass);
@ -111,9 +129,11 @@ public class BeanRegistryAdapter implements BeanRegistry { @@ -111,9 +129,11 @@ public class BeanRegistryAdapter implements BeanRegistry {
}
@Override
public <T> void registerBean(String name, Class<T> beanClass, Consumer<Spec<T>> spec) {
public <T> void registerBean(String name, ParameterizedTypeReference<T> 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 { @@ -122,6 +142,33 @@ public class BeanRegistryAdapter implements BeanRegistry {
this.beanRegistry.registerBeanDefinition(name, beanDefinition);
}
@Override
public <T> void registerBean(String name, Class<T> beanClass, Consumer<Spec<T>> 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 <T> void registerBean(String name, ParameterizedTypeReference<T> beanType, Consumer<Spec<T>> 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 { @@ -238,18 +285,6 @@ public class BeanRegistryAdapter implements BeanRegistry {
supplier.apply(new SupplierContextAdapter(this.beanFactory)));
return this;
}
@Override
public Spec<T> targetType(ParameterizedTypeReference<? extends T> targetType) {
this.beanDefinition.setTargetType(ResolvableType.forType(targetType));
return this;
}
@Override
public Spec<T> targetType(ResolvableType targetType) {
this.beanDefinition.setTargetType(targetType);
return this;
}
}
@ -262,23 +297,28 @@ public class BeanRegistryAdapter implements BeanRegistry { @@ -262,23 +297,28 @@ public class BeanRegistryAdapter implements BeanRegistry {
}
@Override
public <T> T bean(Class<T> requiredType) throws BeansException {
return this.beanFactory.getBean(requiredType);
public <T> T bean(Class<T> beanClass) throws BeansException {
return this.beanFactory.getBean(beanClass);
}
@Override
public <T> T bean(ParameterizedTypeReference<T> beanType) throws BeansException {
return this.beanFactory.getBeanProvider(beanType).getObject();
}
@Override
public <T> T bean(String name, Class<T> requiredType) throws BeansException {
return this.beanFactory.getBean(name, requiredType);
public <T> T bean(String name, Class<T> beanClass) throws BeansException {
return this.beanFactory.getBean(name, beanClass);
}
@Override
public <T> ObjectProvider<T> beanProvider(Class<T> requiredType) {
return this.beanFactory.getBeanProvider(requiredType);
public <T> ObjectProvider<T> beanProvider(Class<T> beanClass) {
return this.beanFactory.getBeanProvider(beanClass);
}
@Override
public <T> ObjectProvider<T> beanProvider(ResolvableType requiredType) {
return this.beanFactory.getBeanProvider(requiredType);
public <T> ObjectProvider<T> beanProvider(ParameterizedTypeReference<T> beanType) {
return this.beanFactory.getBeanProvider(beanType);
}
}

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

@ -168,12 +168,8 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean @@ -168,12 +168,8 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
if (prototype) {
it.prototype()
}
val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference<T>() {});
if (resolvableType.hasGenerics()) {
it.targetType(resolvableType)
}
}
registry.registerBean(name, T::class.java, customizer)
registry.registerBean(name, object: ParameterizedTypeReference<T>() {}, customizer)
}
/**
@ -234,12 +230,8 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean @@ -234,12 +230,8 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
if (prototype) {
it.prototype()
}
val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference<T>() {});
if (resolvableType.hasGenerics()) {
it.targetType(resolvableType)
}
}
return registry.registerBean(T::class.java, customizer)
return registry.registerBean(object: ParameterizedTypeReference<T>() {}, customizer)
}
/**
@ -304,12 +296,8 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean @@ -304,12 +296,8 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
it.supplier {
SupplierContextDsl<T>(it, env).supplier()
}
val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference<T>() {});
if (resolvableType.hasGenerics()) {
it.targetType(resolvableType)
}
}
registry.registerBean(name, T::class.java, customizer)
registry.registerBean(name, object: ParameterizedTypeReference<T>() {}, customizer)
}
inline fun <reified T : Any> registerBean(autowirable: Boolean = true,
@ -372,12 +360,8 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean @@ -372,12 +360,8 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
it.supplier {
SupplierContextDsl<T>(it, env).supplier()
}
val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference<T>() {});
if (resolvableType.hasGenerics()) {
it.targetType(resolvableType)
}
}
return registry.registerBean(T::class.java, customizer)
return registry.registerBean(object: ParameterizedTypeReference<T>() {}, customizer)
}
// Function with 0 parameter
@ -1094,7 +1078,7 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean @@ -1094,7 +1078,7 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
* @return a corresponding provider handle
*/
inline fun <reified T : Any> beanProvider() : ObjectProvider<T> =
context.beanProvider(ResolvableType.forType((object : ParameterizedTypeReference<T>() {}).type))
context.beanProvider(object : ParameterizedTypeReference<T>() {})
}
override fun register(registry: BeanRegistry, env: Environment) {

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

@ -24,7 +24,6 @@ import org.springframework.beans.factory.BeanRegistrar; @@ -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 { @@ -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 { @@ -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<Supplier<Foo>> type = new ParameterizedTypeReference<>() {};
registry.registerBean("fooSupplierFromTypeReference", Supplier.class,
spec -> spec.targetType(type));
registry.registerBean("fooSupplier", new ParameterizedTypeReference<Supplier<Foo>>() {});
}
}

5
spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/GenericBeanRegistrar.java

@ -27,9 +27,8 @@ public class GenericBeanRegistrar implements BeanRegistrar { @@ -27,9 +27,8 @@ public class GenericBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
ParameterizedTypeReference<Supplier<Foo>> type = new ParameterizedTypeReference<>() {};
registry.registerBean("fooSupplier", Supplier.class, spec -> spec.targetType(type)
.supplier(context-> (Supplier<Foo>) Foo::new));
registry.registerBean("fooSupplier", new ParameterizedTypeReference<Supplier<Foo>>() {}, spec ->
spec.supplier(context-> (Supplier<Foo>) Foo::new));
}
public record Foo() {}

Loading…
Cancel
Save