Skip to content

Commit c55362c

Browse files
committed
Provider injection works with generically typed collections of beans as well (SPR-9030)
1 parent 9a61f36 commit c55362c

File tree

4 files changed

+209
-39
lines changed

4 files changed

+209
-39
lines changed

org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -21,6 +21,7 @@
2121
import java.io.Serializable;
2222
import java.lang.annotation.Annotation;
2323
import java.lang.reflect.Field;
24+
import java.lang.reflect.ParameterizedType;
2425
import java.lang.reflect.Type;
2526

2627
import org.springframework.core.GenericCollectionTypeResolver;
@@ -56,6 +57,8 @@ public class DependencyDescriptor implements Serializable {
5657

5758
private final boolean eager;
5859

60+
private int nestingLevel = 1;
61+
5962
private transient Annotation[] fieldAnnotations;
6063

6164

@@ -153,6 +156,13 @@ public boolean isEager() {
153156
}
154157

155158

159+
public void increaseNestingLevel() {
160+
this.nestingLevel++;
161+
if (this.methodParameter != null) {
162+
this.methodParameter.increaseNestingLevel();
163+
}
164+
}
165+
156166
/**
157167
* Initialize parameter name discovery for the underlying method parameter, if any.
158168
* <p>This method does not actually try to retrieve the parameter name at
@@ -178,15 +188,30 @@ public String getDependencyName() {
178188
* @return the declared type (never <code>null</code>)
179189
*/
180190
public Class<?> getDependencyType() {
181-
return (this.field != null ? this.field.getType() : this.methodParameter.getParameterType());
182-
}
183-
184-
/**
185-
* Determine the generic type of the wrapped parameter/field.
186-
* @return the generic type (never <code>null</code>)
187-
*/
188-
public Type getGenericDependencyType() {
189-
return (this.field != null ? this.field.getGenericType() : this.methodParameter.getGenericParameterType());
191+
if (this.field != null) {
192+
if (this.nestingLevel > 1) {
193+
Type type = this.field.getGenericType();
194+
if (type instanceof ParameterizedType) {
195+
Type arg = ((ParameterizedType) type).getActualTypeArguments()[0];
196+
if (arg instanceof Class) {
197+
return (Class) arg;
198+
}
199+
else if (arg instanceof ParameterizedType) {
200+
arg = ((ParameterizedType) arg).getRawType();
201+
if (arg instanceof Class) {
202+
return (Class) arg;
203+
}
204+
}
205+
}
206+
return Object.class;
207+
}
208+
else {
209+
return this.field.getType();
210+
}
211+
}
212+
else {
213+
return this.methodParameter.getNestedParameterType();
214+
}
190215
}
191216

192217
/**
@@ -195,7 +220,7 @@ public Type getGenericDependencyType() {
195220
*/
196221
public Class<?> getCollectionType() {
197222
return (this.field != null ?
198-
GenericCollectionTypeResolver.getCollectionFieldType(this.field) :
223+
GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel) :
199224
GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter));
200225
}
201226

@@ -205,7 +230,7 @@ public Class<?> getCollectionType() {
205230
*/
206231
public Class<?> getMapKeyType() {
207232
return (this.field != null ?
208-
GenericCollectionTypeResolver.getMapKeyFieldType(this.field) :
233+
GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel) :
209234
GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter));
210235
}
211236

@@ -215,7 +240,7 @@ public Class<?> getMapKeyType() {
215240
*/
216241
public Class<?> getMapValueType() {
217242
return (this.field != null ?
218-
GenericCollectionTypeResolver.getMapValueFieldType(this.field) :
243+
GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel) :
219244
GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter));
220245
}
221246

@@ -248,13 +273,18 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound
248273
if (this.fieldName != null) {
249274
this.field = this.declaringClass.getDeclaredField(this.fieldName);
250275
}
251-
else if (this.methodName != null) {
252-
this.methodParameter = new MethodParameter(
253-
this.declaringClass.getDeclaredMethod(this.methodName, this.parameterTypes), this.parameterIndex);
254-
}
255276
else {
256-
this.methodParameter = new MethodParameter(
257-
this.declaringClass.getDeclaredConstructor(this.parameterTypes), this.parameterIndex);
277+
if (this.methodName != null) {
278+
this.methodParameter = new MethodParameter(
279+
this.declaringClass.getDeclaredMethod(this.methodName, this.parameterTypes), this.parameterIndex);
280+
}
281+
else {
282+
this.methodParameter = new MethodParameter(
283+
this.declaringClass.getDeclaredConstructor(this.parameterTypes), this.parameterIndex);
284+
}
285+
for (int i = 1; i < this.nestingLevel; i++) {
286+
this.methodParameter.increaseNestingLevel();
287+
}
258288
}
259289
}
260290
catch (Throwable ex) {

org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -1000,27 +1000,14 @@ private class DependencyObjectFactory implements ObjectFactory, Serializable {
10001000

10011001
private final String beanName;
10021002

1003-
private final Class type;
1004-
10051003
public DependencyObjectFactory(DependencyDescriptor descriptor, String beanName) {
10061004
this.descriptor = descriptor;
10071005
this.beanName = beanName;
1008-
this.type = determineObjectFactoryType();
1009-
}
1010-
1011-
private Class determineObjectFactoryType() {
1012-
Type type = this.descriptor.getGenericDependencyType();
1013-
if (type instanceof ParameterizedType) {
1014-
Type arg = ((ParameterizedType) type).getActualTypeArguments()[0];
1015-
if (arg instanceof Class) {
1016-
return (Class) arg;
1017-
}
1018-
}
1019-
return Object.class;
1006+
this.descriptor.increaseNestingLevel();
10201007
}
10211008

10221009
public Object getObject() throws BeansException {
1023-
return doResolveDependency(this.descriptor, this.type, this.beanName, null, null);
1010+
return doResolveDependency(this.descriptor, this.descriptor.getDependencyType(), this.beanName, null, null);
10241011
}
10251012
}
10261013

org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -23,7 +23,6 @@
2323
import javax.inject.Named;
2424
import javax.inject.Provider;
2525

26-
import static org.junit.Assert.*;
2726
import org.junit.Test;
2827
import test.beans.ITestBean;
2928
import test.beans.IndexedTestBean;
@@ -40,6 +39,8 @@
4039
import org.springframework.beans.factory.support.RootBeanDefinition;
4140
import org.springframework.util.SerializationTestUtils;
4241

42+
import static org.junit.Assert.*;
43+
4344
/**
4445
* Unit tests for {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor}
4546
* processing the JSR-303 {@link javax.inject.Inject} annotation.
@@ -206,8 +207,8 @@ public void testConstructorResourceInjectionWithMultipleCandidatesAsCollection()
206207
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
207208
bpp.setBeanFactory(bf);
208209
bf.addBeanPostProcessor(bpp);
209-
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(
210-
ConstructorsCollectionResourceInjectionBean.class));
210+
bf.registerBeanDefinition("annotatedBean",
211+
new RootBeanDefinition(ConstructorsCollectionResourceInjectionBean.class));
211212
TestBean tb = new TestBean();
212213
bf.registerSingleton("testBean", tb);
213214
NestedTestBean ntb1 = new NestedTestBean();
@@ -415,6 +416,74 @@ public void testObjectFactorySerialization() throws Exception {
415416
bf.destroySingletons();
416417
}
417418

419+
@Test
420+
public void testObjectFactoryWithTypedListField() throws Exception {
421+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
422+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
423+
bpp.setBeanFactory(bf);
424+
bf.addBeanPostProcessor(bpp);
425+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryListFieldInjectionBean.class));
426+
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
427+
bf.setSerializationId("test");
428+
429+
ObjectFactoryListFieldInjectionBean bean = (ObjectFactoryListFieldInjectionBean) bf.getBean("annotatedBean");
430+
assertSame(bf.getBean("testBean"), bean.getTestBean());
431+
bean = (ObjectFactoryListFieldInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean);
432+
assertSame(bf.getBean("testBean"), bean.getTestBean());
433+
bf.destroySingletons();
434+
}
435+
436+
@Test
437+
public void testObjectFactoryWithTypedListMethod() throws Exception {
438+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
439+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
440+
bpp.setBeanFactory(bf);
441+
bf.addBeanPostProcessor(bpp);
442+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryListMethodInjectionBean.class));
443+
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
444+
bf.setSerializationId("test");
445+
446+
ObjectFactoryListMethodInjectionBean bean = (ObjectFactoryListMethodInjectionBean) bf.getBean("annotatedBean");
447+
assertSame(bf.getBean("testBean"), bean.getTestBean());
448+
bean = (ObjectFactoryListMethodInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean);
449+
assertSame(bf.getBean("testBean"), bean.getTestBean());
450+
bf.destroySingletons();
451+
}
452+
453+
@Test
454+
public void testObjectFactoryWithTypedMapField() throws Exception {
455+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
456+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
457+
bpp.setBeanFactory(bf);
458+
bf.addBeanPostProcessor(bpp);
459+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryMapFieldInjectionBean.class));
460+
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
461+
bf.setSerializationId("test");
462+
463+
ObjectFactoryMapFieldInjectionBean bean = (ObjectFactoryMapFieldInjectionBean) bf.getBean("annotatedBean");
464+
assertSame(bf.getBean("testBean"), bean.getTestBean());
465+
bean = (ObjectFactoryMapFieldInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean);
466+
assertSame(bf.getBean("testBean"), bean.getTestBean());
467+
bf.destroySingletons();
468+
}
469+
470+
@Test
471+
public void testObjectFactoryWithTypedMapMethod() throws Exception {
472+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
473+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
474+
bpp.setBeanFactory(bf);
475+
bf.addBeanPostProcessor(bpp);
476+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryMapMethodInjectionBean.class));
477+
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
478+
bf.setSerializationId("test");
479+
480+
ObjectFactoryMapMethodInjectionBean bean = (ObjectFactoryMapMethodInjectionBean) bf.getBean("annotatedBean");
481+
assertSame(bf.getBean("testBean"), bean.getTestBean());
482+
bean = (ObjectFactoryMapMethodInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean);
483+
assertSame(bf.getBean("testBean"), bean.getTestBean());
484+
bf.destroySingletons();
485+
}
486+
418487
/**
419488
* Verifies that a dependency on a {@link org.springframework.beans.factory.FactoryBean} can be autowired via
420489
* {@link org.springframework.beans.factory.annotation.Autowired @Inject}, specifically addressing the JIRA issue
@@ -835,6 +904,66 @@ public TestBean getTestBean() {
835904
}
836905

837906

907+
public static class ObjectFactoryListFieldInjectionBean implements Serializable {
908+
909+
@Inject
910+
private Provider<List<TestBean>> testBeanFactory;
911+
912+
public void setTestBeanFactory(Provider<List<TestBean>> testBeanFactory) {
913+
this.testBeanFactory = testBeanFactory;
914+
}
915+
916+
public TestBean getTestBean() {
917+
return this.testBeanFactory.get().get(0);
918+
}
919+
}
920+
921+
922+
public static class ObjectFactoryListMethodInjectionBean implements Serializable {
923+
924+
private Provider<List<TestBean>> testBeanFactory;
925+
926+
@Inject
927+
public void setTestBeanFactory(Provider<List<TestBean>> testBeanFactory) {
928+
this.testBeanFactory = testBeanFactory;
929+
}
930+
931+
public TestBean getTestBean() {
932+
return this.testBeanFactory.get().get(0);
933+
}
934+
}
935+
936+
937+
public static class ObjectFactoryMapFieldInjectionBean implements Serializable {
938+
939+
@Inject
940+
private Provider<Map<String, TestBean>> testBeanFactory;
941+
942+
public void setTestBeanFactory(Provider<Map<String, TestBean>> testBeanFactory) {
943+
this.testBeanFactory = testBeanFactory;
944+
}
945+
946+
public TestBean getTestBean() {
947+
return this.testBeanFactory.get().values().iterator().next();
948+
}
949+
}
950+
951+
952+
public static class ObjectFactoryMapMethodInjectionBean implements Serializable {
953+
954+
private Provider<Map<String, TestBean>> testBeanFactory;
955+
956+
@Inject
957+
public void setTestBeanFactory(Provider<Map<String, TestBean>> testBeanFactory) {
958+
this.testBeanFactory = testBeanFactory;
959+
}
960+
961+
public TestBean getTestBean() {
962+
return this.testBeanFactory.get().values().iterator().next();
963+
}
964+
}
965+
966+
838967
/**
839968
* Bean with a dependency on a {@link org.springframework.beans.factory.FactoryBean}.
840969
*/

org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.lang.reflect.Constructor;
2222
import java.lang.reflect.Member;
2323
import java.lang.reflect.Method;
24+
import java.lang.reflect.ParameterizedType;
2425
import java.lang.reflect.Type;
2526
import java.lang.reflect.TypeVariable;
2627
import java.util.HashMap;
@@ -233,6 +234,29 @@ public Type getGenericParameterType() {
233234
return this.genericParameterType;
234235
}
235236

237+
public Class<?> getNestedParameterType() {
238+
if (this.nestingLevel > 1) {
239+
Type type = getGenericParameterType();
240+
if (type instanceof ParameterizedType) {
241+
Integer index = getTypeIndexForCurrentLevel();
242+
Type arg = ((ParameterizedType) type).getActualTypeArguments()[index != null ? index : 0];
243+
if (arg instanceof Class) {
244+
return (Class) arg;
245+
}
246+
else if (arg instanceof ParameterizedType) {
247+
arg = ((ParameterizedType) arg).getRawType();
248+
if (arg instanceof Class) {
249+
return (Class) arg;
250+
}
251+
}
252+
}
253+
return Object.class;
254+
}
255+
else {
256+
return getParameterType();
257+
}
258+
}
259+
236260
/**
237261
* Return the annotations associated with the target method/constructor itself.
238262
*/

0 commit comments

Comments
 (0)