Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public class Main {}
}
}

@Test
//@Test
void validateJar() throws IOException {
writeBuildFile("""
plugins {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.springframework.beans.factory;

import java.lang.reflect.Type;

import org.jspecify.annotations.Nullable;

import org.springframework.beans.BeansException;
Expand Down Expand Up @@ -100,6 +102,7 @@
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @author Yanming Zhou
* @since 13 April 2001
* @see BeanNameAware#setBeanName
* @see BeanClassLoaderAware#setBeanClassLoader
Expand Down Expand Up @@ -175,6 +178,37 @@ 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.0
* @see #getBean(String, Class)
*/
@SuppressWarnings("unchecked")
default <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;
}

/**
* Return an instance, which may be shared or independent, of the specified bean.
* <p>Allows for specifying explicit constructor arguments / factory method arguments,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package org.springframework.beans.factory;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import org.springframework.beans.BeansException;
import org.springframework.util.ClassUtils;

Expand All @@ -24,6 +27,7 @@
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Yanming Zhou
*/
@SuppressWarnings("serial")
public class BeanNotOfRequiredTypeException extends BeansException {
Expand All @@ -37,6 +41,8 @@ public class BeanNotOfRequiredTypeException extends BeansException {
/** The offending type. */
private final Class<?> actualType;

/** The required generic type. */
private final Type requiredGenericType;

/**
* Create a new BeanNotOfRequiredTypeException.
Expand All @@ -46,11 +52,32 @@ 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.0
*/
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.requiredGenericType = requiredType;
this.actualType = actualType;
if (requiredType instanceof Class<?> requiredClass) {
this.requiredType = requiredClass;
}
else if (requiredType instanceof ParameterizedType parameterizedType) {
this.requiredType = (Class<?>) parameterizedType.getRawType();
}
else {
throw new IllegalArgumentException(requiredType + " is not supported");
}
}


Expand All @@ -68,6 +95,14 @@ public Class<?> getRequiredType() {
return this.requiredType;
}

/**
* Return the expected generic type for the bean.
* @since 7.0
*/
public Type getRequiredGenericType() {
return this.requiredGenericType;
}

/**
* Return the actual type of the instance found.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,22 @@ 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 =
getBeanProvider<T>().getObject()

/**
* 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
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;
Expand Down Expand Up @@ -1682,6 +1683,29 @@ void getBeanByTypeWithAmbiguity() {
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.getRequiredGenericType().toString()).endsWith("Template<java.lang.String>");
});
}

@Test
void getBeanByTypeWithPrimary() {
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
Expand Down Expand Up @@ -3872,4 +3896,16 @@ public Class<?> getObjectType() {
}
}

private static class Template<T> {

}

private static class StringTemplate extends Template<String> {

}

private static class NumberTemplate extends Template<Number> {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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
Expand Down