Generic bean type resolution for lookup methods

Closes gh-26998
This commit is contained in:
Juergen Hoeller 2021-05-28 17:23:52 +02:00
parent b18f8771c8
commit b3dcb64ff1
6 changed files with 102 additions and 23 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -35,6 +35,7 @@ import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy; import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp; import org.springframework.cglib.proxy.NoOp;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -244,8 +245,10 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
return (bean.equals(null) ? null : bean); return (bean.equals(null) ? null : bean);
} }
else { else {
return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) : // Find target bean matching the (potentially generic) method return type
this.owner.getBean(method.getReturnType())); ResolvableType genericReturnType = ResolvableType.forMethodReturnType(method);
return (argsToUse != null ? this.owner.getBeanProvider(genericReturnType).getObject(argsToUse) :
this.owner.getBeanProvider(genericReturnType).getObject());
} }
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,17 +19,25 @@ package org.springframework.beans.factory.support;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
/** /**
* Represents an override of a method that looks up an object in the same IoC context. * Represents an override of a method that looks up an object in the same IoC context,
* either by bean name or by bean type (based on the declared method return type).
* *
* <p>Methods eligible for lookup override must not have arguments. * <p>Methods eligible for lookup override may declare arguments in which case the
* given arguments are passed to the bean retrieval operation.
* *
* @author Rod Johnson * @author Rod Johnson
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 1.1 * @since 1.1
* @see org.springframework.beans.factory.BeanFactory#getBean(String)
* @see org.springframework.beans.factory.BeanFactory#getBean(Class)
* @see org.springframework.beans.factory.BeanFactory#getBean(String, Object...)
* @see org.springframework.beans.factory.BeanFactory#getBean(Class, Object...)
* @see org.springframework.beans.factory.BeanFactory#getBeanProvider(ResolvableType)
*/ */
public class LookupOverride extends MethodOverride { public class LookupOverride extends MethodOverride {
@ -43,8 +51,8 @@ public class LookupOverride extends MethodOverride {
/** /**
* Construct a new LookupOverride. * Construct a new LookupOverride.
* @param methodName the name of the method to override * @param methodName the name of the method to override
* @param beanName the name of the bean in the current {@code BeanFactory} * @param beanName the name of the bean in the current {@code BeanFactory} that the
* that the overridden method should return (may be {@code null}) * overridden method should return (may be {@code null} for type-based bean retrieval)
*/ */
public LookupOverride(String methodName, @Nullable String beanName) { public LookupOverride(String methodName, @Nullable String beanName) {
super(methodName); super(methodName);
@ -53,9 +61,9 @@ public class LookupOverride extends MethodOverride {
/** /**
* Construct a new LookupOverride. * Construct a new LookupOverride.
* @param method the method to override * @param method the method declaration to override
* @param beanName the name of the bean in the current {@code BeanFactory} * @param beanName the name of the bean in the current {@code BeanFactory} that the
* that the overridden method should return (may be {@code null}) * overridden method should return (may be {@code null} for type-based bean retrieval)
*/ */
public LookupOverride(Method method, @Nullable String beanName) { public LookupOverride(Method method, @Nullable String beanName) {
super(method.getName()); super(method.getName());

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,9 +19,8 @@ package org.springframework.beans.factory.support;
import java.lang.reflect.Method; import java.lang.reflect.Method;
/** /**
* Interface to be implemented by classes that can reimplement any method * Interface to be implemented by classes that can reimplement any method on an
* on an IoC-managed object: the <b>Method Injection</b> form of * IoC-managed object: the <b>Method Injection</b> form of Dependency Injection.
* Dependency Injection.
* *
* <p>Such methods may be (but need not be) abstract, in which case the * <p>Such methods may be (but need not be) abstract, in which case the
* container will create a concrete subclass to instantiate. * container will create a concrete subclass to instantiate.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,7 +25,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
/** /**
* Extension of MethodOverride that represents an arbitrary * Extension of {@link MethodOverride} that represents an arbitrary
* override of a method by the IoC container. * override of a method by the IoC container.
* *
* <p>Any non-final method can be overridden, irrespective of its * <p>Any non-final method can be overridden, irrespective of its
@ -45,7 +45,7 @@ public class ReplaceOverride extends MethodOverride {
/** /**
* Construct a new ReplaceOverride. * Construct a new ReplaceOverride.
* @param methodName the name of the method to override * @param methodName the name of the method to override
* @param methodReplacerBeanName the bean name of the MethodReplacer * @param methodReplacerBeanName the bean name of the {@link MethodReplacer}
*/ */
public ReplaceOverride(String methodName, String methodReplacerBeanName) { public ReplaceOverride(String methodName, String methodReplacerBeanName) {
super(methodName); super(methodName);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -121,6 +121,18 @@ public class LookupAnnotationTests {
assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean); assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean);
} }
@Test
public void testWithGenericBean() {
beanFactory.registerBeanDefinition("numberBean", new RootBeanDefinition(NumberBean.class));
beanFactory.registerBeanDefinition("doubleStore", new RootBeanDefinition(DoubleStore.class));
beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class));
NumberBean bean = (NumberBean) beanFactory.getBean("numberBean");
assertThat(bean).isNotNull();
assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore());
assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore());
}
public static abstract class AbstractBean { public static abstract class AbstractBean {
@ -147,4 +159,26 @@ public class LookupAnnotationTests {
AbstractBean abstractBean; AbstractBean abstractBean;
} }
public static class NumberStore<T extends Number> {
}
public static class DoubleStore extends NumberStore<Double> {
}
public static class FloatStore extends NumberStore<Float> {
}
public static abstract class NumberBean {
@Lookup
public abstract NumberStore<Double> getDoubleStore();
@Lookup
public abstract NumberStore<Float> getFloatStore();
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -36,7 +36,7 @@ public class LookupMethodTests {
@BeforeEach @BeforeEach
public void setUp() { public void setup() {
beanFactory = new DefaultListableBeanFactory(); beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(new ClassPathResource("lookupMethodTests.xml", getClass())); reader.loadBeanDefinitions(new ClassPathResource("lookupMethodTests.xml", getClass()));
@ -83,8 +83,8 @@ public class LookupMethodTests {
public void testWithThreeArgsShouldFail() { public void testWithThreeArgsShouldFail() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertThat(bean).isNotNull(); assertThat(bean).isNotNull();
assertThatExceptionOfType(AbstractMethodError.class).as("does not have a three arg constructor").isThrownBy(() -> assertThatExceptionOfType(AbstractMethodError.class).as("does not have a three arg constructor")
bean.getThreeArguments("name", 1, 2)); .isThrownBy(() -> bean.getThreeArguments("name", 1, 2));
} }
@Test @Test
@ -97,6 +97,21 @@ public class LookupMethodTests {
assertThat(expected.isJedi()).isTrue(); assertThat(expected.isJedi()).isTrue();
} }
@Test
public void testWithGenericBean() {
RootBeanDefinition bd = new RootBeanDefinition(NumberBean.class);
bd.getMethodOverrides().addOverride(new LookupOverride("getDoubleStore", null));
bd.getMethodOverrides().addOverride(new LookupOverride("getFloatStore", null));
beanFactory.registerBeanDefinition("numberBean", bd);
beanFactory.registerBeanDefinition("doubleStore", new RootBeanDefinition(DoubleStore.class));
beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class));
NumberBean bean = (NumberBean) beanFactory.getBean("numberBean");
assertThat(bean).isNotNull();
assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore());
assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore());
}
public static abstract class AbstractBean { public static abstract class AbstractBean {
@ -111,4 +126,24 @@ public class LookupMethodTests {
public abstract TestBean getThreeArguments(String name, int age, int anotherArg); public abstract TestBean getThreeArguments(String name, int age, int anotherArg);
} }
public static class NumberStore<T extends Number> {
}
public static class DoubleStore extends NumberStore<Double> {
}
public static class FloatStore extends NumberStore<Float> {
}
public static abstract class NumberBean {
public abstract NumberStore<Double> getDoubleStore();
public abstract NumberStore<Float> getFloatStore();
}
} }