Skip to content

Commit b3dcb64

Browse files
committed
Generic bean type resolution for lookup methods
Closes gh-26998
1 parent b18f877 commit b3dcb64

File tree

6 files changed

+102
-23
lines changed

6 files changed

+102
-23
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@
3535
import org.springframework.cglib.proxy.MethodInterceptor;
3636
import org.springframework.cglib.proxy.MethodProxy;
3737
import org.springframework.cglib.proxy.NoOp;
38+
import org.springframework.core.ResolvableType;
3839
import org.springframework.lang.Nullable;
3940
import org.springframework.util.Assert;
4041
import org.springframework.util.StringUtils;
@@ -244,8 +245,10 @@ public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp
244245
return (bean.equals(null) ? null : bean);
245246
}
246247
else {
247-
return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) :
248-
this.owner.getBean(method.getReturnType()));
248+
// Find target bean matching the (potentially generic) method return type
249+
ResolvableType genericReturnType = ResolvableType.forMethodReturnType(method);
250+
return (argsToUse != null ? this.owner.getBeanProvider(genericReturnType).getObject(argsToUse) :
251+
this.owner.getBeanProvider(genericReturnType).getObject());
249252
}
250253
}
251254
}

spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,17 +19,25 @@
1919
import java.lang.reflect.Method;
2020
import java.lang.reflect.Modifier;
2121

22+
import org.springframework.core.ResolvableType;
2223
import org.springframework.lang.Nullable;
2324
import org.springframework.util.ObjectUtils;
2425

2526
/**
26-
* Represents an override of a method that looks up an object in the same IoC context.
27+
* Represents an override of a method that looks up an object in the same IoC context,
28+
* either by bean name or by bean type (based on the declared method return type).
2729
*
28-
* <p>Methods eligible for lookup override must not have arguments.
30+
* <p>Methods eligible for lookup override may declare arguments in which case the
31+
* given arguments are passed to the bean retrieval operation.
2932
*
3033
* @author Rod Johnson
3134
* @author Juergen Hoeller
3235
* @since 1.1
36+
* @see org.springframework.beans.factory.BeanFactory#getBean(String)
37+
* @see org.springframework.beans.factory.BeanFactory#getBean(Class)
38+
* @see org.springframework.beans.factory.BeanFactory#getBean(String, Object...)
39+
* @see org.springframework.beans.factory.BeanFactory#getBean(Class, Object...)
40+
* @see org.springframework.beans.factory.BeanFactory#getBeanProvider(ResolvableType)
3341
*/
3442
public class LookupOverride extends MethodOverride {
3543

@@ -43,8 +51,8 @@ public class LookupOverride extends MethodOverride {
4351
/**
4452
* Construct a new LookupOverride.
4553
* @param methodName the name of the method to override
46-
* @param beanName the name of the bean in the current {@code BeanFactory}
47-
* that the overridden method should return (may be {@code null})
54+
* @param beanName the name of the bean in the current {@code BeanFactory} that the
55+
* overridden method should return (may be {@code null} for type-based bean retrieval)
4856
*/
4957
public LookupOverride(String methodName, @Nullable String beanName) {
5058
super(methodName);
@@ -53,9 +61,9 @@ public LookupOverride(String methodName, @Nullable String beanName) {
5361

5462
/**
5563
* Construct a new LookupOverride.
56-
* @param method the method to override
57-
* @param beanName the name of the bean in the current {@code BeanFactory}
58-
* that the overridden method should return (may be {@code null})
64+
* @param method the method declaration to override
65+
* @param beanName the name of the bean in the current {@code BeanFactory} that the
66+
* overridden method should return (may be {@code null} for type-based bean retrieval)
5967
*/
6068
public LookupOverride(Method method, @Nullable String beanName) {
6169
super(method.getName());

spring-beans/src/main/java/org/springframework/beans/factory/support/MethodReplacer.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,9 +19,8 @@
1919
import java.lang.reflect.Method;
2020

2121
/**
22-
* Interface to be implemented by classes that can reimplement any method
23-
* on an IoC-managed object: the <b>Method Injection</b> form of
24-
* Dependency Injection.
22+
* Interface to be implemented by classes that can reimplement any method on an
23+
* IoC-managed object: the <b>Method Injection</b> form of Dependency Injection.
2524
*
2625
* <p>Such methods may be (but need not be) abstract, in which case the
2726
* container will create a concrete subclass to instantiate.

spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@
2525
import org.springframework.util.ObjectUtils;
2626

2727
/**
28-
* Extension of MethodOverride that represents an arbitrary
28+
* Extension of {@link MethodOverride} that represents an arbitrary
2929
* override of a method by the IoC container.
3030
*
3131
* <p>Any non-final method can be overridden, irrespective of its
@@ -45,7 +45,7 @@ public class ReplaceOverride extends MethodOverride {
4545
/**
4646
* Construct a new ReplaceOverride.
4747
* @param methodName the name of the method to override
48-
* @param methodReplacerBeanName the bean name of the MethodReplacer
48+
* @param methodReplacerBeanName the bean name of the {@link MethodReplacer}
4949
*/
5050
public ReplaceOverride(String methodName, String methodReplacerBeanName) {
5151
super(methodName);

spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -121,6 +121,18 @@ public void testWithNullBean() {
121121
assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean);
122122
}
123123

124+
@Test
125+
public void testWithGenericBean() {
126+
beanFactory.registerBeanDefinition("numberBean", new RootBeanDefinition(NumberBean.class));
127+
beanFactory.registerBeanDefinition("doubleStore", new RootBeanDefinition(DoubleStore.class));
128+
beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class));
129+
130+
NumberBean bean = (NumberBean) beanFactory.getBean("numberBean");
131+
assertThat(bean).isNotNull();
132+
assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore());
133+
assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore());
134+
}
135+
124136

125137
public static abstract class AbstractBean {
126138

@@ -147,4 +159,26 @@ public static class BeanConsumer {
147159
AbstractBean abstractBean;
148160
}
149161

162+
163+
public static class NumberStore<T extends Number> {
164+
}
165+
166+
167+
public static class DoubleStore extends NumberStore<Double> {
168+
}
169+
170+
171+
public static class FloatStore extends NumberStore<Float> {
172+
}
173+
174+
175+
public static abstract class NumberBean {
176+
177+
@Lookup
178+
public abstract NumberStore<Double> getDoubleStore();
179+
180+
@Lookup
181+
public abstract NumberStore<Float> getFloatStore();
182+
}
183+
150184
}

spring-beans/src/test/java/org/springframework/beans/factory/support/LookupMethodTests.java

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ public class LookupMethodTests {
3636

3737

3838
@BeforeEach
39-
public void setUp() {
39+
public void setup() {
4040
beanFactory = new DefaultListableBeanFactory();
4141
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
4242
reader.loadBeanDefinitions(new ClassPathResource("lookupMethodTests.xml", getClass()));
@@ -83,8 +83,8 @@ public void testWithTwoConstructorArg() {
8383
public void testWithThreeArgsShouldFail() {
8484
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
8585
assertThat(bean).isNotNull();
86-
assertThatExceptionOfType(AbstractMethodError.class).as("does not have a three arg constructor").isThrownBy(() ->
87-
bean.getThreeArguments("name", 1, 2));
86+
assertThatExceptionOfType(AbstractMethodError.class).as("does not have a three arg constructor")
87+
.isThrownBy(() -> bean.getThreeArguments("name", 1, 2));
8888
}
8989

9090
@Test
@@ -97,6 +97,21 @@ public void testWithOverriddenLookupMethod() {
9797
assertThat(expected.isJedi()).isTrue();
9898
}
9999

100+
@Test
101+
public void testWithGenericBean() {
102+
RootBeanDefinition bd = new RootBeanDefinition(NumberBean.class);
103+
bd.getMethodOverrides().addOverride(new LookupOverride("getDoubleStore", null));
104+
bd.getMethodOverrides().addOverride(new LookupOverride("getFloatStore", null));
105+
beanFactory.registerBeanDefinition("numberBean", bd);
106+
beanFactory.registerBeanDefinition("doubleStore", new RootBeanDefinition(DoubleStore.class));
107+
beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class));
108+
109+
NumberBean bean = (NumberBean) beanFactory.getBean("numberBean");
110+
assertThat(bean).isNotNull();
111+
assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore());
112+
assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore());
113+
}
114+
100115

101116
public static abstract class AbstractBean {
102117

@@ -111,4 +126,24 @@ public static abstract class AbstractBean {
111126
public abstract TestBean getThreeArguments(String name, int age, int anotherArg);
112127
}
113128

129+
130+
public static class NumberStore<T extends Number> {
131+
}
132+
133+
134+
public static class DoubleStore extends NumberStore<Double> {
135+
}
136+
137+
138+
public static class FloatStore extends NumberStore<Float> {
139+
}
140+
141+
142+
public static abstract class NumberBean {
143+
144+
public abstract NumberStore<Double> getDoubleStore();
145+
146+
public abstract NumberStore<Float> getFloatStore();
147+
}
148+
114149
}

0 commit comments

Comments
 (0)