From 1e16aecadd169badfe257353b51db59005369877 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 8 Sep 2009 20:55:00 +0000 Subject: [PATCH] BeanFactory supports ObjectFactory as a dependency type for @Autowired and @Value (SPR-6079) git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1850 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../factory/config/DependencyDescriptor.java | 69 +++++++++++++- .../support/DefaultListableBeanFactory.java | 59 ++++++++++-- ...wiredAnnotationBeanPostProcessorTests.java | 93 +++++++++++++++++-- .../ApplicationContextExpressionTests.java | 15 ++- .../springframework/core/MethodParameter.java | 26 +++++- 5 files changed, 235 insertions(+), 27 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 2c64c60637b..1a3a2c0bcd8 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 @@ -16,8 +16,12 @@ package org.springframework.beans.factory.config; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.Type; import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.MethodParameter; @@ -32,17 +36,27 @@ import org.springframework.util.Assert; * @author Juergen Hoeller * @since 2.5 */ -public class DependencyDescriptor { +public class DependencyDescriptor implements Serializable { - private MethodParameter methodParameter; + private transient MethodParameter methodParameter; - private Field field; + private transient Field field; + + private Class declaringClass; + + private String methodName; + + private Class[] parameterTypes; + + private int parameterIndex; + + private String fieldName; private final boolean required; private final boolean eager; - private Annotation[] fieldAnnotations; + private transient Annotation[] fieldAnnotations; /** @@ -65,6 +79,15 @@ public class DependencyDescriptor { public DependencyDescriptor(MethodParameter methodParameter, boolean required, boolean eager) { Assert.notNull(methodParameter, "MethodParameter must not be null"); this.methodParameter = methodParameter; + this.declaringClass = methodParameter.getDeclaringClass(); + if (this.methodParameter.getMethod() != null) { + this.methodName = methodParameter.getMethod().getName(); + this.parameterTypes = methodParameter.getMethod().getParameterTypes(); + } + else { + this.parameterTypes = methodParameter.getConstructor().getParameterTypes(); + } + this.parameterIndex = methodParameter.getParameterIndex(); this.required = required; this.eager = eager; } @@ -89,6 +112,8 @@ public class DependencyDescriptor { public DependencyDescriptor(Field field, boolean required, boolean eager) { Assert.notNull(field, "Field must not be null"); this.field = field; + this.declaringClass = field.getDeclaringClass(); + this.fieldName = field.getName(); this.required = required; this.eager = eager; } @@ -156,6 +181,14 @@ public class DependencyDescriptor { 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()); + } + /** * Determine the generic element type of the wrapped Collection parameter/field, if any. * @return the generic type, or null if none @@ -201,4 +234,32 @@ public class DependencyDescriptor { } } + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + // Rely on default serialization; just initialize state after deserialization. + ois.defaultReadObject(); + + // Restore reflective handles (which are unfortunately not serializable) + try { + 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); + } + } + catch (Throwable ex) { + throw new IllegalStateException("Could not find original class structure", 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 94b4680ed1d..e5869b0e1c1 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 @@ -21,6 +21,8 @@ import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.ref.Reference; import java.lang.ref.WeakReference; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; @@ -508,14 +510,13 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (isFactoryBean(beanName)) { final FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); - boolean isEagerInit = false; - + boolean isEagerInit; if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { isEagerInit = AccessController.doPrivileged(new PrivilegedAction() { public Boolean run() { - return Boolean.valueOf(((SmartFactoryBean) factory).isEagerInit()); + return ((SmartFactoryBean) factory).isEagerInit(); } - }, getAccessControlContext()).booleanValue(); + }, getAccessControlContext()); } else { isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean) factory).isEagerInit(); @@ -634,14 +635,23 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto //--------------------------------------------------------------------- - // Implementation of superclass abstract methods + // Dependency resolution functionality //--------------------------------------------------------------------- public Object resolveDependency(DependencyDescriptor descriptor, String beanName, Set autowiredBeanNames, TypeConverter typeConverter) throws BeansException { descriptor.initParameterNameDiscovery(getParameterNameDiscoverer()); - Class type = descriptor.getDependencyType(); + if (ObjectFactory.class.equals(descriptor.getDependencyType())) { + return new DependencyObjectFactory(descriptor, beanName); + } + else { + return doResolveDependency(descriptor, descriptor.getDependencyType(), beanName, autowiredBeanNames, typeConverter); + } + } + + protected Object doResolveDependency(DependencyDescriptor descriptor, Class type, String beanName, + Set autowiredBeanNames, TypeConverter typeConverter) throws BeansException { Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { @@ -649,7 +659,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto String strVal = resolveEmbeddedValue((String) value); value = evaluateBeanDefinitionString(strVal, getMergedBeanDefinition(beanName)); } - return typeConverter.convertIfNecessary(value, type); + TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); + return converter.convertIfNecessary(value, type); } if (type.isArray()) { @@ -907,4 +918,38 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } } + + /** + * Serializable ObjectFactory for lazy resolution of a dependency. + */ + private class DependencyObjectFactory implements ObjectFactory, Serializable { + + private final DependencyDescriptor descriptor; + + 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; + } + + public Object getObject() throws BeansException { + return doResolveDependency(this.descriptor, this.type, this.beanName, null, null); + } + } + } diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index f49d16d5377..4e21315ecdc 100644 --- a/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -16,8 +16,7 @@ package org.springframework.beans.factory.annotation; -import static org.junit.Assert.*; - +import java.io.Serializable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -25,20 +24,24 @@ import java.lang.annotation.Target; import java.util.List; import java.util.Map; +import static org.junit.Assert.*; import org.junit.Test; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.beans.factory.support.RootBeanDefinition; - import test.beans.ITestBean; import test.beans.IndexedTestBean; import test.beans.NestedTestBean; import test.beans.TestBean; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.util.SerializationTestUtils; + /** * Unit tests for {@link AutowiredAnnotationBeanPostProcessor}. * @@ -553,6 +556,53 @@ public final class AutowiredAnnotationBeanPostProcessorTests { bf.destroySingletons(); } + @Test + public void testObjectFactoryInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + + ObjectFactoryInjectionBean bean = (ObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactoryQualifierInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryQualifierInjectionBean.class)); + RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); + bd.addQualifier(new AutowireCandidateQualifier(Qualifier.class, "testBean")); + bf.registerBeanDefinition("testBean", bd); + + ObjectFactoryQualifierInjectionBean bean = (ObjectFactoryQualifierInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactorySerialization() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryInjectionBean.class)); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + bf.setSerializationId("test"); + + ObjectFactoryInjectionBean bean = (ObjectFactoryInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bean = (ObjectFactoryInjectionBean) SerializationTestUtils.serializeAndDeserialize(bean); + assertSame(bf.getBean("testBean"), bean.getTestBean()); + bf.destroySingletons(); + } + @Test public void testCustomAnnotationRequiredFieldResourceInjection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -1218,6 +1268,29 @@ public final class AutowiredAnnotationBeanPostProcessorTests { } + public static class ObjectFactoryInjectionBean implements Serializable { + + @Autowired + private ObjectFactory testBeanFactory; + + public TestBean getTestBean() { + return this.testBeanFactory.getObject(); + } + } + + + public static class ObjectFactoryQualifierInjectionBean { + + @Autowired + //@Qualifier("testBean") + private ObjectFactory testBeanFactory; + + public TestBean getTestBean() { + return (TestBean) this.testBeanFactory.getObject(); + } + } + + public static class CustomAnnotationRequiredFieldResourceInjectionBean { @MyAutowired(optional = false) diff --git a/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java b/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java index e202ebd7538..43d9fefec53 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java @@ -17,6 +17,7 @@ package org.springframework.context.expression; import java.util.Properties; +import java.io.Serializable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -37,6 +38,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; import org.springframework.util.StopWatch; +import org.springframework.util.SerializationTestUtils; /** * @author Juergen Hoeller @@ -47,7 +49,7 @@ public class ApplicationContextExpressionTests { private static final Log factoryLog = LogFactory.getLog(DefaultListableBeanFactory.class); @Test - public void genericApplicationContext() { + public void genericApplicationContext() throws Exception { GenericApplicationContext ac = new GenericApplicationContext(); AnnotationConfigUtils.registerAnnotationConfigProcessors(ac); @@ -139,8 +141,12 @@ public class ApplicationContextExpressionTests { assertEquals("XXXmyNameYYY42ZZZ", tb3.name); assertEquals(42, tb3.age); assertEquals("123 UK", tb3.country); + assertEquals("123 UK", tb3.countryFactory.getObject()); assertSame(tb0, tb3.tb); + tb3 = (ValueTestBean) SerializationTestUtils.serializeAndDeserialize(tb3); + assertEquals("123 UK", tb3.countryFactory.getObject()); + ConstructorValueTestBean tb4 = ac.getBean("tb4", ConstructorValueTestBean.class); assertEquals("XXXmyNameYYY42ZZZ", tb4.name); assertEquals(42, tb4.age); @@ -198,7 +204,7 @@ public class ApplicationContextExpressionTests { } - public static class ValueTestBean { + public static class ValueTestBean implements Serializable { @Autowired @Value("XXX#{tb0.name}YYY#{mySpecialAttr}ZZZ") public String name; @@ -209,8 +215,11 @@ public class ApplicationContextExpressionTests { @Value("${code} #{systemProperties.country}") public String country; + @Value("${code} #{systemProperties.country}") + public ObjectFactory countryFactory; + @Autowired @Qualifier("original") - public TestBean tb; + public transient TestBean tb; } 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 ee5bb3e08b9..8f8cfe35673 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 @@ -49,7 +49,9 @@ public class MethodParameter { private final int parameterIndex; - private Class parameterType; + private Class parameterType; + + private Type genericParameterType; private Annotation[] parameterAnnotations; @@ -167,7 +169,7 @@ public class MethodParameter { /** * Set a resolved (generic) parameter type. */ - void setParameterType(Class parameterType) { + void setParameterType(Class parameterType) { this.parameterType = parameterType; } @@ -175,7 +177,7 @@ public class MethodParameter { * Return the type of the method/constructor parameter. * @return the parameter type (never null) */ - public Class getParameterType() { + public Class getParameterType() { if (this.parameterType == null) { if (this.parameterIndex < 0) { this.parameterType = (this.method != null ? this.method.getReturnType() : null); @@ -189,6 +191,24 @@ public class MethodParameter { return this.parameterType; } + /** + * Return the generic type of the method/constructor parameter. + * @return the parameter type (never null) + */ + public Type getGenericParameterType() { + if (this.genericParameterType == null) { + if (this.parameterIndex < 0) { + this.genericParameterType = (this.method != null ? this.method.getGenericReturnType() : null); + } + else { + this.genericParameterType = (this.method != null ? + this.method.getGenericParameterTypes()[this.parameterIndex] : + this.constructor.getGenericParameterTypes()[this.parameterIndex]); + } + } + return this.genericParameterType; + } + /** * Return the annotations associated with the target method/constructor itself. */