From c55362c35e0c098d3e7fb9c5c749e180382bbca8 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 8 Feb 2012 16:29:00 +0100 Subject: [PATCH] Provider injection works with generically typed collections of beans as well (SPR-9030) --- .../factory/config/DependencyDescriptor.java | 68 ++++++--- .../support/DefaultListableBeanFactory.java | 19 +-- ...njectAnnotationBeanPostProcessorTests.java | 137 +++++++++++++++++- .../springframework/core/MethodParameter.java | 24 +++ 4 files changed, 209 insertions(+), 39 deletions(-) diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index 1a3a2c0bcd..a54002c0b0 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.io.ObjectInputStream; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import org.springframework.core.GenericCollectionTypeResolver; @@ -56,6 +57,8 @@ public class DependencyDescriptor implements Serializable { private final boolean eager; + private int nestingLevel = 1; + private transient Annotation[] fieldAnnotations; @@ -153,6 +156,13 @@ public class DependencyDescriptor implements Serializable { } + public void increaseNestingLevel() { + this.nestingLevel++; + if (this.methodParameter != null) { + this.methodParameter.increaseNestingLevel(); + } + } + /** * Initialize parameter name discovery for the underlying method parameter, if any. *

This method does not actually try to retrieve the parameter name at @@ -178,15 +188,30 @@ public class DependencyDescriptor implements Serializable { * @return the declared type (never null) */ public Class getDependencyType() { - return (this.field != null ? this.field.getType() : this.methodParameter.getParameterType()); - } - - /** - * Determine the generic type of the wrapped parameter/field. - * @return the generic type (never null) - */ - public Type getGenericDependencyType() { - return (this.field != null ? this.field.getGenericType() : this.methodParameter.getGenericParameterType()); + if (this.field != null) { + if (this.nestingLevel > 1) { + Type type = this.field.getGenericType(); + if (type instanceof ParameterizedType) { + Type arg = ((ParameterizedType) type).getActualTypeArguments()[0]; + if (arg instanceof Class) { + return (Class) arg; + } + else if (arg instanceof ParameterizedType) { + arg = ((ParameterizedType) arg).getRawType(); + if (arg instanceof Class) { + return (Class) arg; + } + } + } + return Object.class; + } + else { + return this.field.getType(); + } + } + else { + return this.methodParameter.getNestedParameterType(); + } } /** @@ -195,7 +220,7 @@ public class DependencyDescriptor implements Serializable { */ public Class getCollectionType() { return (this.field != null ? - GenericCollectionTypeResolver.getCollectionFieldType(this.field) : + GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel) : GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter)); } @@ -205,7 +230,7 @@ public class DependencyDescriptor implements Serializable { */ public Class getMapKeyType() { return (this.field != null ? - GenericCollectionTypeResolver.getMapKeyFieldType(this.field) : + GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel) : GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter)); } @@ -215,7 +240,7 @@ public class DependencyDescriptor implements Serializable { */ public Class getMapValueType() { return (this.field != null ? - GenericCollectionTypeResolver.getMapValueFieldType(this.field) : + GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel) : GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter)); } @@ -248,13 +273,18 @@ public class DependencyDescriptor implements Serializable { if (this.fieldName != null) { this.field = this.declaringClass.getDeclaredField(this.fieldName); } - else if (this.methodName != null) { - this.methodParameter = new MethodParameter( - this.declaringClass.getDeclaredMethod(this.methodName, this.parameterTypes), this.parameterIndex); - } else { - this.methodParameter = new MethodParameter( - this.declaringClass.getDeclaredConstructor(this.parameterTypes), this.parameterIndex); + if (this.methodName != null) { + this.methodParameter = new MethodParameter( + this.declaringClass.getDeclaredMethod(this.methodName, this.parameterTypes), this.parameterIndex); + } + else { + this.methodParameter = new MethodParameter( + this.declaringClass.getDeclaredConstructor(this.parameterTypes), this.parameterIndex); + } + for (int i = 1; i < this.nestingLevel; i++) { + this.methodParameter.increaseNestingLevel(); + } } } catch (Throwable ex) { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 55c4c7860b..6f7e75c9e5 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1000,27 +1000,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto private final String beanName; - private final Class type; - public DependencyObjectFactory(DependencyDescriptor descriptor, String beanName) { this.descriptor = descriptor; this.beanName = beanName; - this.type = determineObjectFactoryType(); - } - - private Class determineObjectFactoryType() { - Type type = this.descriptor.getGenericDependencyType(); - if (type instanceof ParameterizedType) { - Type arg = ((ParameterizedType) type).getActualTypeArguments()[0]; - if (arg instanceof Class) { - return (Class) arg; - } - } - return Object.class; + this.descriptor.increaseNestingLevel(); } public Object getObject() throws BeansException { - return doResolveDependency(this.descriptor, this.type, this.beanName, null, null); + return doResolveDependency(this.descriptor, this.descriptor.getDependencyType(), this.beanName, null, null); } } diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java index 0621e87415..51ae86cd6c 100644 --- a/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; -import static org.junit.Assert.*; import org.junit.Test; import test.beans.ITestBean; import test.beans.IndexedTestBean; @@ -40,6 +39,8 @@ import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.util.SerializationTestUtils; +import static org.junit.Assert.*; + /** * Unit tests for {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor} * processing the JSR-303 {@link javax.inject.Inject} annotation. @@ -206,8 +207,8 @@ public class InjectAnnotationBeanPostProcessorTests { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition( - ConstructorsCollectionResourceInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", + new RootBeanDefinition(ConstructorsCollectionResourceInjectionBean.class)); TestBean tb = new TestBean(); bf.registerSingleton("testBean", tb); NestedTestBean ntb1 = new NestedTestBean(); @@ -415,6 +416,74 @@ public class InjectAnnotationBeanPostProcessorTests { bf.destroySingletons(); } + @Test + public void testObjectFactoryWithTypedListField() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryListFieldInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + + ObjectFactoryListFieldInjectionBean bean = (ObjectFactoryListFieldInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bean = (ObjectFactoryListFieldInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactoryWithTypedListMethod() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryListMethodInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + + ObjectFactoryListMethodInjectionBean bean = (ObjectFactoryListMethodInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bean = (ObjectFactoryListMethodInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactoryWithTypedMapField() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryMapFieldInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + + ObjectFactoryMapFieldInjectionBean bean = (ObjectFactoryMapFieldInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bean = (ObjectFactoryMapFieldInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactoryWithTypedMapMethod() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryMapMethodInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + + ObjectFactoryMapMethodInjectionBean bean = (ObjectFactoryMapMethodInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bean = (ObjectFactoryMapMethodInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + /** * Verifies that a dependency on a {@link org.springframework.beans.factory.FactoryBean} can be autowired via * {@link org.springframework.beans.factory.annotation.Autowired @Inject}, specifically addressing the JIRA issue @@ -835,6 +904,66 @@ public class InjectAnnotationBeanPostProcessorTests { } + public static class ObjectFactoryListFieldInjectionBean implements Serializable { + + @Inject + private Provider> testBeanFactory; + + public void setTestBeanFactory(Provider> testBeanFactory) { + this.testBeanFactory = testBeanFactory; + } + + public TestBean getTestBean() { + return this.testBeanFactory.get().get(0); + } + } + + + public static class ObjectFactoryListMethodInjectionBean implements Serializable { + + private Provider> testBeanFactory; + + @Inject + public void setTestBeanFactory(Provider> testBeanFactory) { + this.testBeanFactory = testBeanFactory; + } + + public TestBean getTestBean() { + return this.testBeanFactory.get().get(0); + } + } + + + public static class ObjectFactoryMapFieldInjectionBean implements Serializable { + + @Inject + private Provider> testBeanFactory; + + public void setTestBeanFactory(Provider> testBeanFactory) { + this.testBeanFactory = testBeanFactory; + } + + public TestBean getTestBean() { + return this.testBeanFactory.get().values().iterator().next(); + } + } + + + public static class ObjectFactoryMapMethodInjectionBean implements Serializable { + + private Provider> testBeanFactory; + + @Inject + public void setTestBeanFactory(Provider> testBeanFactory) { + this.testBeanFactory = testBeanFactory; + } + + public TestBean getTestBean() { + return this.testBeanFactory.get().values().iterator().next(); + } + } + + /** * Bean with a dependency on a {@link org.springframework.beans.factory.FactoryBean}. */ diff --git a/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java b/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java index 361bae7eed..9c33cb2578 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java @@ -21,6 +21,7 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.HashMap; @@ -233,6 +234,29 @@ public class MethodParameter { return this.genericParameterType; } + public Class getNestedParameterType() { + if (this.nestingLevel > 1) { + Type type = getGenericParameterType(); + if (type instanceof ParameterizedType) { + Integer index = getTypeIndexForCurrentLevel(); + Type arg = ((ParameterizedType) type).getActualTypeArguments()[index != null ? index : 0]; + if (arg instanceof Class) { + return (Class) arg; + } + else if (arg instanceof ParameterizedType) { + arg = ((ParameterizedType) arg).getRawType(); + if (arg instanceof Class) { + return (Class) arg; + } + } + } + return Object.class; + } + else { + return getParameterType(); + } + } + /** * Return the annotations associated with the target method/constructor itself. */ -- GitLab