mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-28 18:19:05 +00:00
Add typesafe method to get generic bean by name with type reference
Fix GH-34687 Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
This commit is contained in:
committed by
Juergen Hoeller
parent
a5579614d9
commit
0eba6f0da3
@@ -100,6 +100,7 @@ import org.springframework.core.ResolvableType;
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @author Yanming Zhou
|
||||
* @since 13 April 2001
|
||||
* @see BeanNameAware#setBeanName
|
||||
* @see BeanClassLoaderAware#setBeanClassLoader
|
||||
@@ -175,6 +176,29 @@ public interface BeanFactory {
|
||||
*/
|
||||
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
|
||||
|
||||
/**
|
||||
* Return an instance, which may be shared or independent, of the specified bean.
|
||||
* <p>Behaves the same as {@link #getBean(String)}, but provides a measure of type
|
||||
* safety by throwing a BeanNotOfRequiredTypeException if the bean is not of the
|
||||
* required type. This means that ClassCastException can't be thrown on casting
|
||||
* the result correctly, as can happen with {@link #getBean(String)}.
|
||||
* <p>Translates aliases back to the corresponding canonical bean name.
|
||||
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
|
||||
* @param name the name of the bean to retrieve
|
||||
* @param typeReference the reference to obtain type the bean must match
|
||||
* @return an instance of the bean.
|
||||
* Note that the return value will never be {@code null}. In case of a stub for
|
||||
* {@code null} from a factory method having been resolved for the requested bean, a
|
||||
* {@code BeanNotOfRequiredTypeException} against the NullBean stub will be raised.
|
||||
* Consider using {@link #getBeanProvider(Class)} for resolving optional dependencies.
|
||||
* @throws NoSuchBeanDefinitionException if there is no such bean definition
|
||||
* @throws BeanNotOfRequiredTypeException if the bean is not of the required type
|
||||
* @throws BeansException if the bean could not be created
|
||||
* @since 7.1
|
||||
* @see #getBean(String, Class)
|
||||
*/
|
||||
<T> T getBean(String name, ParameterizedTypeReference<T> typeReference) throws BeansException;
|
||||
|
||||
/**
|
||||
* Return an instance, which may be shared or independent, of the specified bean.
|
||||
* <p>Allows for specifying explicit constructor arguments / factory method arguments,
|
||||
|
||||
+28
-4
@@ -16,7 +16,10 @@
|
||||
|
||||
package org.springframework.beans.factory;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
@@ -24,6 +27,7 @@ import org.springframework.util.ClassUtils;
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class BeanNotOfRequiredTypeException extends BeansException {
|
||||
@@ -32,7 +36,7 @@ public class BeanNotOfRequiredTypeException extends BeansException {
|
||||
private final String beanName;
|
||||
|
||||
/** The required type. */
|
||||
private final Class<?> requiredType;
|
||||
private final Type genericRequiredType;
|
||||
|
||||
/** The offending type. */
|
||||
private final Class<?> actualType;
|
||||
@@ -46,10 +50,22 @@ public class BeanNotOfRequiredTypeException extends BeansException {
|
||||
* the expected type
|
||||
*/
|
||||
public BeanNotOfRequiredTypeException(String beanName, Class<?> requiredType, Class<?> actualType) {
|
||||
super("Bean named '" + beanName + "' is expected to be of type '" + ClassUtils.getQualifiedName(requiredType) +
|
||||
this(beanName, (Type) requiredType, actualType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new BeanNotOfRequiredTypeException.
|
||||
* @param beanName the name of the bean requested
|
||||
* @param requiredType the required type
|
||||
* @param actualType the actual type returned, which did not match
|
||||
* the expected type
|
||||
* @since 7.1
|
||||
*/
|
||||
public BeanNotOfRequiredTypeException(String beanName, Type requiredType, Class<?> actualType) {
|
||||
super("Bean named '" + beanName + "' is expected to be of type '" + requiredType.getTypeName() +
|
||||
"' but was actually of type '" + ClassUtils.getQualifiedName(actualType) + "'");
|
||||
this.beanName = beanName;
|
||||
this.requiredType = requiredType;
|
||||
this.genericRequiredType = requiredType;
|
||||
this.actualType = actualType;
|
||||
}
|
||||
|
||||
@@ -65,7 +81,15 @@ public class BeanNotOfRequiredTypeException extends BeansException {
|
||||
* Return the expected type for the bean.
|
||||
*/
|
||||
public Class<?> getRequiredType() {
|
||||
return this.requiredType;
|
||||
return (this.genericRequiredType instanceof Class<?> clazz ? clazz : ResolvableType.forType(this.genericRequiredType).toClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the expected generic type for the bean.
|
||||
* @since 7.1
|
||||
*/
|
||||
public Type getGenericRequiredType() {
|
||||
return this.genericRequiredType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+13
@@ -17,6 +17,7 @@
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.beans.PropertyEditor;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -66,6 +67,7 @@ import org.springframework.beans.factory.config.Scope;
|
||||
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.core.DecoratingClassLoader;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
@@ -201,6 +203,17 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
return doGetBean(name, requiredType, null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getBean(String name, ParameterizedTypeReference<T> typeReference) throws BeansException {
|
||||
Object bean = getBean(name);
|
||||
Type requiredType = typeReference.getType();
|
||||
if (!ResolvableType.forType(requiredType).isInstance(bean)) {
|
||||
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
|
||||
}
|
||||
return (T) bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBean(String name, @Nullable Object @Nullable ... args) throws BeansException {
|
||||
return doGetBean(name, null, args, false);
|
||||
|
||||
+13
@@ -17,6 +17,7 @@
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -64,6 +65,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @author Yanming Zhou
|
||||
* @since 06.01.2003
|
||||
* @see DefaultListableBeanFactory
|
||||
*/
|
||||
@@ -149,6 +151,17 @@ public class StaticListableBeanFactory implements ListableBeanFactory {
|
||||
return (T) bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getBean(String name, ParameterizedTypeReference<T> typeReference) throws BeansException {
|
||||
Object bean = getBean(name);
|
||||
Type requiredType = typeReference.getType();
|
||||
if (!ResolvableType.forType(requiredType).isInstance(bean)) {
|
||||
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
|
||||
}
|
||||
return (T) bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBean(String name, @Nullable Object @Nullable ... args) throws BeansException {
|
||||
if (!ObjectUtils.isEmpty(args)) {
|
||||
|
||||
+3
-2
@@ -24,6 +24,7 @@ import org.springframework.core.ResolvableType
|
||||
* This extension is not subject to type erasure and retains actual generic type arguments.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Yanming Zhou
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> BeanFactory.getBean(): T =
|
||||
@@ -31,14 +32,14 @@ inline fun <reified T : Any> BeanFactory.getBean(): T =
|
||||
|
||||
/**
|
||||
* Extension for [BeanFactory.getBean] providing a `getBean<Foo>("foo")` variant.
|
||||
* Like the original Java method, this extension is subject to type erasure.
|
||||
* This extension is not subject to type erasure and retains actual generic type arguments.
|
||||
*
|
||||
* @see BeanFactory.getBean(String, Class<T>)
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
inline fun <reified T : Any> BeanFactory.getBean(name: String): T =
|
||||
getBean(name, T::class.java)
|
||||
getBean(name, (object : ParameterizedTypeReference<T>() {}))
|
||||
|
||||
/**
|
||||
* Extension for [BeanFactory.getBean] providing a `getBean<Foo>(arg1, arg2)` variant.
|
||||
|
||||
+36
@@ -79,6 +79,7 @@ import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.DummyFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -1682,6 +1683,29 @@ class DefaultListableBeanFactoryTests {
|
||||
lbf.getBean(TestBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBeanByNameWithTypeReference() {
|
||||
RootBeanDefinition bd1 = new RootBeanDefinition(StringTemplate.class);
|
||||
RootBeanDefinition bd2 = new RootBeanDefinition(NumberTemplate.class);
|
||||
lbf.registerBeanDefinition("bd1", bd1);
|
||||
lbf.registerBeanDefinition("bd2", bd2);
|
||||
|
||||
Template<String> stringTemplate = lbf.getBean("bd1", new ParameterizedTypeReference<>() {});
|
||||
Template<Number> numberTemplate = lbf.getBean("bd2", new ParameterizedTypeReference<>() {});
|
||||
|
||||
assertThat(stringTemplate).isInstanceOf(StringTemplate.class);
|
||||
assertThat(numberTemplate).isInstanceOf(NumberTemplate.class);
|
||||
|
||||
assertThatExceptionOfType(BeanNotOfRequiredTypeException.class)
|
||||
.isThrownBy(() -> lbf.getBean("bd2", new ParameterizedTypeReference<Template<String>>() {}))
|
||||
.satisfies(ex -> {
|
||||
assertThat(ex.getBeanName()).isEqualTo("bd2");
|
||||
assertThat(ex.getRequiredType()).isEqualTo(Template.class);
|
||||
assertThat(ex.getActualType()).isEqualTo(NumberTemplate.class);
|
||||
assertThat(ex.getGenericRequiredType().toString()).endsWith("Template<java.lang.String>");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBeanByTypeWithPrimary() {
|
||||
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
|
||||
@@ -3872,4 +3896,16 @@ class DefaultListableBeanFactoryTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Template<T> {
|
||||
|
||||
}
|
||||
|
||||
private static class StringTemplate extends Template<String> {
|
||||
|
||||
}
|
||||
|
||||
private static class NumberTemplate extends Template<Number> {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-1
@@ -21,6 +21,7 @@ import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.core.ParameterizedTypeReference
|
||||
import org.springframework.core.ResolvableType
|
||||
|
||||
/**
|
||||
@@ -53,7 +54,16 @@ class BeanFactoryExtensionsTests {
|
||||
fun `getBean with String and reified type parameters`() {
|
||||
val name = "foo"
|
||||
bf.getBean<Foo>(name)
|
||||
verify { bf.getBean(name, Foo::class.java) }
|
||||
verify { bf.getBean(name, ofType<ParameterizedTypeReference<Foo>>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getBean with String and reified generic type parameters`() {
|
||||
val name = "foo"
|
||||
val foo = listOf(Foo())
|
||||
every { bf.getBean(name, ofType<ParameterizedTypeReference<List<Foo>>>()) } returns foo
|
||||
assertThat(bf.getBean<List<Foo>>("foo")).isSameAs(foo)
|
||||
verify { bf.getBean(name, ofType<ParameterizedTypeReference<List<Foo>>>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+7
@@ -132,6 +132,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @author Sam Brannen
|
||||
* @author Sebastien Deleuze
|
||||
* @author Brian Clozel
|
||||
* @author Yanming Zhou
|
||||
* @since January 21, 2001
|
||||
* @see #refreshBeanFactory
|
||||
* @see #getBeanFactory
|
||||
@@ -1305,6 +1306,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
return getBeanFactory().getBean(name, requiredType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getBean(String name, ParameterizedTypeReference<T> typeReference) throws BeansException {
|
||||
assertBeanFactoryActive();
|
||||
return getBeanFactory().getBean(name, typeReference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBean(String name, @Nullable Object @Nullable ... args) throws BeansException {
|
||||
assertBeanFactoryActive();
|
||||
|
||||
+13
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.jndi.support;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -59,6 +60,7 @@ import org.springframework.jndi.TypeMismatchNamingException;
|
||||
* in particular if BeanFactory-style type checking is required.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Yanming Zhou
|
||||
* @since 2.5
|
||||
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory
|
||||
* @see org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
|
||||
@@ -132,6 +134,17 @@ public class SimpleJndiBeanFactory extends JndiLocatorSupport implements BeanFac
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getBean(String name, ParameterizedTypeReference<T> typeReference) throws BeansException {
|
||||
Object bean = getBean(name);
|
||||
Type requiredType = typeReference.getType();
|
||||
if (!ResolvableType.forType(requiredType).isInstance(bean)) {
|
||||
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
|
||||
}
|
||||
return (T) bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBean(String name, @Nullable Object @Nullable ... args) throws BeansException {
|
||||
if (args != null) {
|
||||
|
||||
+6
@@ -65,6 +65,7 @@ import org.springframework.web.context.support.ServletContextResourcePatternReso
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @author Yanming Zhou
|
||||
* @since 3.2
|
||||
*/
|
||||
class StubWebApplicationContext implements WebApplicationContext {
|
||||
@@ -168,6 +169,11 @@ class StubWebApplicationContext implements WebApplicationContext {
|
||||
return this.beanFactory.getBean(name, requiredType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getBean(String name, ParameterizedTypeReference<T> typeReference) throws BeansException {
|
||||
return this.beanFactory.getBean(name, typeReference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBean(String name, @Nullable Object @Nullable ... args) throws BeansException {
|
||||
return this.beanFactory.getBean(name, args);
|
||||
|
||||
Reference in New Issue
Block a user