Browse Source

Add typesafe method to get generic bean by name with type reference

Fix GH-34687

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
pull/36543/head
Yanming Zhou 1 year ago committed by Juergen Hoeller
parent
commit
0eba6f0da3
  1. 24
      spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java
  2. 32
      spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java
  3. 13
      spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java
  4. 13
      spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java
  5. 5
      spring-beans/src/main/kotlin/org/springframework/beans/factory/BeanFactoryExtensions.kt
  6. 36
      spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
  7. 12
      spring-beans/src/test/kotlin/org/springframework/beans/factory/BeanFactoryExtensionsTests.kt
  8. 7
      spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
  9. 13
      spring-context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java
  10. 6
      spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java

24
spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java

@ -100,6 +100,7 @@ import org.springframework.core.ResolvableType; @@ -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 { @@ -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,

32
spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java

@ -16,7 +16,10 @@ @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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
spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java

@ -17,6 +17,7 @@ @@ -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; @@ -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 @@ -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
spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java

@ -17,6 +17,7 @@ @@ -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; @@ -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 { @@ -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)) {

5
spring-beans/src/main/kotlin/org/springframework/beans/factory/BeanFactoryExtensions.kt

@ -24,6 +24,7 @@ import org.springframework.core.ResolvableType @@ -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 = @@ -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
spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java

@ -79,6 +79,7 @@ import org.springframework.beans.testfixture.beans.TestBean; @@ -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 { @@ -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 { @@ -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> {
}
}

12
spring-beans/src/test/kotlin/org/springframework/beans/factory/BeanFactoryExtensionsTests.kt

@ -21,6 +21,7 @@ import io.mockk.mockk @@ -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 { @@ -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
spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java

@ -132,6 +132,7 @@ import org.springframework.util.ReflectionUtils; @@ -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 @@ -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
spring-context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java

@ -16,6 +16,7 @@ @@ -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; @@ -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 @@ -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
spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java

@ -65,6 +65,7 @@ import org.springframework.web.context.support.ServletContextResourcePatternReso @@ -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 { @@ -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);

Loading…
Cancel
Save