Browse Source

Add support for target type to BeanRegistry

Closes gh-34560
pull/34571/head
Sébastien Deleuze 9 months ago
parent
commit
a0e2d3a221
  1. 16
      spring-beans/src/main/java/org/springframework/beans/factory/BeanRegistry.java
  2. 13
      spring-beans/src/main/java/org/springframework/beans/factory/support/BeanRegistryAdapter.java
  3. 16
      spring-beans/src/main/kotlin/org/springframework/beans/factory/BeanRegistrarDsl.kt
  4. 30
      spring-beans/src/test/java/org/springframework/beans/factory/support/BeanRegistryAdapterTests.java
  5. 12
      spring-context/src/test/java/org/springframework/context/annotation/beanregistrar/BeanRegistrarConfigurationTests.java
  6. 25
      spring-context/src/test/kotlin/org/springframework/context/annotation/BeanRegistrarDslConfigurationTests.kt
  7. 36
      spring-context/src/testFixtures/java/org/springframework/context/testfixture/beans/factory/GenericBeanRegistrar.java
  8. 26
      spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/registrar/GenericBeanRegistrarConfiguration.java

16
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.BeansException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition; 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.ResolvableType;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -149,6 +151,20 @@ public interface BeanRegistry {
* @see AbstractBeanDefinition#setInstanceSupplier(Supplier) * @see AbstractBeanDefinition#setInstanceSupplier(Supplier)
*/ */
Spec<T> supplier(Function<SupplierContext, T> 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);
} }
/** /**

13
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.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionCustomizer; import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
@ -211,6 +212,18 @@ public class BeanRegistryAdapter implements BeanRegistry {
supplier.apply(new SupplierContextAdapter(this.beanFactory))); supplier.apply(new SupplierContextAdapter(this.beanFactory)));
return this; 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;
}
} }
static class SupplierContextAdapter implements SupplierContext { static class SupplierContextAdapter implements SupplierContext {

16
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) { if (prototype) {
it.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, T::class.java, customizer)
} }
@ -184,6 +188,10 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
if (prototype) { if (prototype) {
it.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(T::class.java, customizer)
} }
@ -250,6 +258,10 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
it.supplier { it.supplier {
SupplierContextDsl<T>(it).supplier() SupplierContextDsl<T>(it).supplier()
} }
val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference<T>() {});
if (resolvableType.hasGenerics()) {
it.targetType(resolvableType)
}
} }
registry.registerBean(name, T::class.java, customizer) registry.registerBean(name, T::class.java, customizer)
} }
@ -314,6 +326,10 @@ open class BeanRegistrarDsl(private val init: BeanRegistrarDsl.() -> Unit): Bean
it.supplier { it.supplier {
SupplierContextDsl<T>(it).supplier() SupplierContextDsl<T>(it).supplier()
} }
val resolvableType = ResolvableType.forType(object: ParameterizedTypeReference<T>() {});
if (resolvableType.hasGenerics()) {
it.targetType(resolvableType)
}
} }
return registry.registerBean(T::class.java, customizer) return registry.registerBean(T::class.java, customizer)
} }

30
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.BeanRegistrar;
import org.springframework.beans.factory.BeanRegistry; import org.springframework.beans.factory.BeanRegistry;
import org.springframework.beans.factory.config.BeanDefinition; 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.Environment;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
@ -203,6 +205,22 @@ public class BeanRegistryAdapterTests {
assertThat(supplier.get()).isNotNull().isInstanceOf(Foo.class); 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 { 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<Supplier<Foo>> type = new ParameterizedTypeReference<>() {};
registry.registerBean("fooSupplierFromTypeReference", Supplier.class,
spec -> spec.targetType(type));
}
}
private static class Foo {} private static class Foo {}
} }

12
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.BeanRegistrar;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; 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.Bar;
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Baz; 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.Foo;
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Init; 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.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.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -69,4 +72,13 @@ public class BeanRegistrarConfigurationTests {
assertThat(context.getBean(Init.class).initialized).isTrue(); 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);
}
} }

25
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.Assertions.assertThatThrownBy
import org.assertj.core.api.ThrowableAssert import org.assertj.core.api.ThrowableAssert
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.springframework.beans.factory.BeanRegistrarDsl
import org.springframework.beans.factory.InitializingBean import org.springframework.beans.factory.InitializingBean
import org.springframework.beans.factory.NoSuchBeanDefinitionException import org.springframework.beans.factory.NoSuchBeanDefinitionException
import org.springframework.beans.factory.config.BeanDefinition import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.getBean 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]. * Kotlin tests leveraging [BeanRegistrarDsl].
@ -56,6 +58,13 @@ class BeanRegistrarDslConfigurationTests {
assertThat(context.getBean<Init>().initialized).isTrue() assertThat(context.getBean<Init>().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 class Foo
data class Bar(val foo: Foo) data class Bar(val foo: Foo)
data class Baz(val message: String = "") data class Baz(val message: String = "")
@ -86,4 +95,18 @@ class BeanRegistrarDslConfigurationTests {
} }
registerBean<Init>() registerBean<Init>()
}) })
@Configuration
@Import(GenericBeanRegistrar::class)
internal class GenericBeanRegistrarKotlinConfiguration
private class GenericBeanRegistrar : BeanRegistrarDsl({
registerBean<Supplier<Foo>>(name = "fooSupplier") {
object: Supplier<Foo> {
override fun get(): Foo {
return Foo()
}
}
}
})
} }

36
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<Supplier<Foo>> type = new ParameterizedTypeReference<>() {};
registry.registerBean("fooSupplier", Supplier.class, spec -> spec.targetType(type)
.supplier(context-> (Supplier<Foo>) Foo::new));
}
public record Foo() {}
}

26
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 {
}
Loading…
Cancel
Save