diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index fffac6f73dc..f80b35b2d7d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -57,7 +57,6 @@ import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -436,10 +435,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean private Object resolvedCachedArgument(String beanName, Object cachedArgument) { if (cachedArgument instanceof DependencyDescriptor) { DependencyDescriptor descriptor = (DependencyDescriptor) cachedArgument; - TypeConverter typeConverter = this.beanFactory.getTypeConverter(); - Object value = this.beanFactory.resolveDependency(descriptor, beanName, null, typeConverter); - AnnotationAwareOrderComparator.sortIfNecessary(value); - return value; + return this.beanFactory.resolveDependency(descriptor, beanName, null, null); } else if (cachedArgument instanceof RuntimeBeanReference) { return this.beanFactory.getBean(((RuntimeBeanReference) cachedArgument).getBeanName()); @@ -479,7 +475,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean Set autowiredBeanNames = new LinkedHashSet(1); TypeConverter typeConverter = beanFactory.getTypeConverter(); value = beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter); - AnnotationAwareOrderComparator.sortIfNecessary(value); synchronized (this) { if (!this.cached) { if (value != null || this.required) { @@ -557,7 +552,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean arguments = null; break; } - AnnotationAwareOrderComparator.sortIfNecessary(arg); arguments[i] = arg; } synchronized (this) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java index 2a7f122f9d0..0d5ef19a1b9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -131,6 +131,10 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan this.beanFactory = beanFactory; } + protected final BeanFactory getBeanFactory() { + return this.beanFactory; + } + /** * Determine whether the provided bean definition is an autowire candidate. @@ -336,4 +340,14 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan return value; } + + /** + * This implementation always returns {@code null}, + * leaving lazy resolution support up to subclasses. + */ + @Override + public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) { + return null; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java index cd2c41e1153..2c3e15fcc8b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,8 +23,8 @@ import org.springframework.beans.factory.config.DependencyDescriptor; * Strategy interface for determining whether a specific bean definition * qualifies as an autowire candidate for a specific dependency. * - * @author Mark Fisher * @author Juergen Hoeller + * @author Mark Fisher * @since 2.5 */ public interface AutowireCandidateResolver { @@ -47,4 +47,15 @@ public interface AutowireCandidateResolver { */ Object getSuggestedValue(DependencyDescriptor descriptor); + /** + * Build a proxy for lazy resolution of the actual dependency target, + * if demanded by the injection point. + * @param descriptor the descriptor for the target method parameter or field + * @param beanName the name of the bean that contains the injection point + * @return the lazy resolution proxy for the actual dependency target, + * or {@code null} if straight resolution is to be performed + * @since 4.0 + */ + Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName); + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index f06f32075bc..3191bc44900 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -29,6 +29,8 @@ import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -125,6 +127,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto /** Whether to allow eager class loading even for lazy-init beans */ private boolean allowEagerClassLoading = true; + /** Optional OrderComparator for dependency Lists and arrays */ + private Comparator dependencyComparator; + /** Resolver to use for checking if a bean definition is an autowire candidate */ private AutowireCandidateResolver autowireCandidateResolver = new SimpleAutowireCandidateResolver(); @@ -205,6 +210,22 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto this.allowEagerClassLoading = allowEagerClassLoading; } + /** + * Set a {@link java.util.Comparator} for dependency Lists and arrays. + * @see org.springframework.core.OrderComparator + * @see org.springframework.core.annotation.AnnotationAwareOrderComparator + */ + public void setDependencyComparator(Comparator dependencyComparator) { + this.dependencyComparator = dependencyComparator; + } + + /** + * Return the dependency comparator for this BeanFactory (may be {@code null}. + */ + public Comparator getDependencyComparator() { + return this.dependencyComparator; + } + /** * Set a custom autowire candidate resolver for this BeanFactory to use * when deciding whether a bean definition should be considered as a @@ -786,13 +807,18 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return new DependencyProviderFactory().createDependencyProvider(descriptor, beanName); } else { - return doResolveDependency(descriptor, descriptor.getDependencyType(), beanName, autowiredBeanNames, typeConverter); + Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, beanName); + if (result == null) { + result = doResolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter); + } + return result; } } - protected Object doResolveDependency(DependencyDescriptor descriptor, Class type, String beanName, + public Object doResolveDependency(DependencyDescriptor descriptor, String beanName, Set autowiredBeanNames, TypeConverter typeConverter) throws BeansException { + Class type = descriptor.getDependencyType(); Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { if (value instanceof String) { @@ -819,7 +845,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto autowiredBeanNames.addAll(matchingBeans.keySet()); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); - return converter.convertIfNecessary(matchingBeans.values(), type); + Object result = converter.convertIfNecessary(matchingBeans.values(), type); + if (this.dependencyComparator != null && result instanceof Object[]) { + Arrays.sort((Object[]) result, this.dependencyComparator); + } + return result; } else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { Class elementType = descriptor.getCollectionType(); @@ -840,7 +870,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto autowiredBeanNames.addAll(matchingBeans.keySet()); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); - return converter.convertIfNecessary(matchingBeans.values(), type); + Object result = converter.convertIfNecessary(matchingBeans.values(), type); + if (this.dependencyComparator != null && result instanceof List) { + Collections.sort((List) result, this.dependencyComparator); + } + return result; } else if (Map.class.isAssignableFrom(type) && type.isInterface()) { Class keyType = descriptor.getMapKeyType(); @@ -1091,7 +1125,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override public Object getObject() throws BeansException { - return doResolveDependency(this.descriptor, this.descriptor.getDependencyType(), this.beanName, null, null); + return doResolveDependency(this.descriptor, this.beanName, null, null); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java index 3570305d9df..4d29b95b6e2 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java @@ -1,6 +1,5 @@ - /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -47,4 +46,9 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve return null; } + @Override + public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) { + return null; + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index d5f8de6f417..13117b16dc8 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -37,6 +37,7 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.Order; import org.springframework.tests.sample.beans.ITestBean; import org.springframework.tests.sample.beans.IndexedTestBean; @@ -46,16 +47,13 @@ import org.springframework.util.SerializationTestUtils; import static org.junit.Assert.*; - /** - * Unit tests for {@link AutowiredAnnotationBeanPostProcessor}. - * * @author Juergen Hoeller * @author Mark Fisher * @author Sam Brannen * @author Chris Beams */ -public final class AutowiredAnnotationBeanPostProcessorTests { +public class AutowiredAnnotationBeanPostProcessorTests { @Test public void testIncompleteBeanDefinition() { @@ -348,6 +346,7 @@ public final class AutowiredAnnotationBeanPostProcessorTests { @Test public void testOrderedResourceInjection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); @@ -381,6 +380,7 @@ public final class AutowiredAnnotationBeanPostProcessorTests { @Test public void testAnnotationOrderedResourceInjection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); @@ -412,6 +412,7 @@ public final class AutowiredAnnotationBeanPostProcessorTests { @Test public void testOrderedCollectionResourceInjection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); @@ -452,6 +453,7 @@ public final class AutowiredAnnotationBeanPostProcessorTests { @Test public void testAnnotationOrderedCollectionResourceInjection() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); @@ -566,6 +568,55 @@ public final class AutowiredAnnotationBeanPostProcessorTests { bf.destroySingletons(); } + @Test + public void testConstructorResourceInjectionWithMultipleOrderedCandidates() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorsResourceInjectionBean.class)); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + FixedOrder2NestedTestBean ntb1 = new FixedOrder2NestedTestBean(); + bf.registerSingleton("nestedTestBean1", ntb1); + FixedOrder1NestedTestBean ntb2 = new FixedOrder1NestedTestBean(); + bf.registerSingleton("nestedTestBean2", ntb2); + + ConstructorsResourceInjectionBean bean = (ConstructorsResourceInjectionBean) bf.getBean("annotatedBean"); + assertNull(bean.getTestBean3()); + assertSame(tb, bean.getTestBean4()); + assertEquals(2, bean.getNestedTestBeans().length); + assertSame(ntb2, bean.getNestedTestBeans()[0]); + assertSame(ntb1, bean.getNestedTestBeans()[1]); + bf.destroySingletons(); + } + + @Test + public void testConstructorResourceInjectionWithMultipleCandidatesAsOrderedCollection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition( + ConstructorsCollectionResourceInjectionBean.class)); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + FixedOrder2NestedTestBean ntb1 = new FixedOrder2NestedTestBean(); + bf.registerSingleton("nestedTestBean1", ntb1); + FixedOrder1NestedTestBean ntb2 = new FixedOrder1NestedTestBean(); + bf.registerSingleton("nestedTestBean2", ntb2); + + ConstructorsCollectionResourceInjectionBean bean = (ConstructorsCollectionResourceInjectionBean) bf.getBean("annotatedBean"); + assertNull(bean.getTestBean3()); + assertSame(tb, bean.getTestBean4()); + assertEquals(2, bean.getNestedTestBeans().size()); + assertSame(ntb2, bean.getNestedTestBeans().get(0)); + assertSame(ntb1, bean.getNestedTestBeans().get(1)); + bf.destroySingletons(); + } + @Test public void testConstructorResourceInjectionWithMultipleCandidatesAndFallback() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -1102,7 +1153,6 @@ public final class AutowiredAnnotationBeanPostProcessorTests { private TestBean testBean2; - @Autowired public void setTestBean2(TestBean testBean2) { if (this.testBean2 != null) { diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/CustomAutowireConfigurerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/CustomAutowireConfigurerTests.java index 4fbb7b8cb1d..228201fadf5 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/CustomAutowireConfigurerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/CustomAutowireConfigurerTests.java @@ -16,10 +16,8 @@ package org.springframework.beans.factory.annotation; -import static org.junit.Assert.assertEquals; -import static org.springframework.tests.TestResourceUtils.qualifiedResource; - import org.junit.Test; + import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.support.AutowireCandidateResolver; @@ -28,6 +26,9 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.Resource; +import static org.junit.Assert.*; +import static org.springframework.tests.TestResourceUtils.*; + /** * Unit tests for {@link CustomAutowireConfigurer}. * @@ -87,6 +88,11 @@ public final class CustomAutowireConfigurerTests { public Object getSuggestedValue(DependencyDescriptor descriptor) { return null; } + + @Override + public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) { + return null; + } } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java index 1457fa7bbcf..bf8c0deab63 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java @@ -26,8 +26,11 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.util.ClassUtils; @@ -35,14 +38,16 @@ import org.springframework.util.ClassUtils; * Utility class that allows for convenient registration of common * {@link org.springframework.beans.factory.config.BeanPostProcessor} and * {@link org.springframework.beans.factory.config.BeanFactoryPostProcessor} - * definitions for annotation-based configuration. + * definitions for annotation-based configuration. Also registers a common + * {@link org.springframework.beans.factory.support.AutowireCandidateResolver}. * * @author Mark Fisher * @author Juergen Hoeller * @author Chris Beams * @since 2.5 + * @see ContextAnnotationAutowireCandidateResolver * @see CommonAnnotationBeanPostProcessor - * @see org.springframework.context.annotation.ConfigurationClassPostProcessor + * @see ConfigurationClassPostProcessor * @see org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor * @see org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor * @see org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor @@ -176,6 +181,16 @@ public class AnnotationConfigUtils { public static Set registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, Object source) { + DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry); + if (beanFactory != null) { + if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { + beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + } + if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { + beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); + } + } + Set beanDefs = new LinkedHashSet(4); if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { @@ -229,6 +244,18 @@ public class AnnotationConfigUtils { return new BeanDefinitionHolder(definition, beanName); } + private static DefaultListableBeanFactory unwrapDefaultListableBeanFactory(BeanDefinitionRegistry registry) { + if (registry instanceof DefaultListableBeanFactory) { + return (DefaultListableBeanFactory) registry; + } + else if (registry instanceof GenericApplicationContext) { + return ((GenericApplicationContext) registry).getDefaultListableBeanFactory(); + } + else { + return null; + } + } + static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) { processCommonDefinitionAnnotations(abd, abd.getMetadata()); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ContextAnnotationAutowireCandidateResolver.java b/spring-context/src/main/java/org/springframework/context/annotation/ContextAnnotationAutowireCandidateResolver.java new file mode 100644 index 00000000000..d6fb3e0452d --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ContextAnnotationAutowireCandidateResolver.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; + +/** + * Complete implementation of the {@link org.springframework.beans.factory.support.AutowireCandidateResolver} + * strategy interface, providing support for qualifier annotations as well as for lazy resolution driven + * by the {@link Lazy} annotation in the {@code context.annotation} package. + * + * @author Juergen Hoeller + * @since 4.0 + */ +public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver { + + @Override + public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) { + return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null); + } + + protected boolean isLazy(DependencyDescriptor descriptor) { + for (Annotation ann : descriptor.getAnnotations()) { + Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class); + if (lazy != null && lazy.value()) { + return true; + } + } + MethodParameter methodParam = descriptor.getMethodParameter(); + if (methodParam != null) { + Method method = methodParam.getMethod(); + if (method == null || void.class.equals(method.getReturnType())) { + Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class); + if (lazy != null && lazy.value()) { + return true; + } + } + } + return false; + } + + protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final String beanName) { + Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory, + "BeanFactory needs to be a DefaultListableBeanFactory"); + final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory(); + TargetSource ts = new TargetSource() { + @Override + public Class getTargetClass() { + return descriptor.getDependencyType(); + } + @Override + public boolean isStatic() { + return false; + } + @Override + public Object getTarget() { + return beanFactory.doResolveDependency(descriptor, beanName, null, null); + } + @Override + public void releaseTarget(Object target) { + } + }; + ProxyFactory pf = new ProxyFactory(); + pf.setTargetSource(ts); + Class dependencyType = descriptor.getDependencyType(); + if (dependencyType.isInterface()) { + pf.addInterface(dependencyType); + } + return pf.getProxy(beanFactory.getBeanClassLoader()); + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Lazy.java b/spring-context/src/main/java/org/springframework/context/annotation/Lazy.java index c8134b3edf4..81a0745431a 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Lazy.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Lazy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -42,14 +42,21 @@ import java.lang.annotation.Target; * method within a {@code @Lazy}-annotated {@code @Configuration} class, this indicates * overriding the 'default lazy' behavior and that the bean should be eagerly initialized. * + *

In addition to its role for component initialization, this annotation may also be placed + * on injection points marked with {@link org.springframework.beans.factory.annotation.Autowired} + * or {@link javax.inject.Inject}: In that context, it leads to the creation of a + * lazy-resolution proxy for all affected dependencies, as an alternative to using + * {@link org.springframework.beans.factory.ObjectFactory} or {@link javax.inject.Provider}. + * * @author Chris Beams + * @author Juergen Hoeller * @since 3.0 * @see Primary * @see Bean * @see Configuration * @see org.springframework.stereotype.Component */ -@Target({ElementType.TYPE, ElementType.METHOD}) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Lazy { diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java index 9fe74c206a6..131e0138812 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java @@ -19,7 +19,6 @@ package org.springframework.context.support; import java.io.IOException; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; @@ -222,7 +221,6 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl if (this.allowCircularReferences != null) { beanFactory.setAllowCircularReferences(this.allowCircularReferences); } - beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); } /** diff --git a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java index 43887d8bf9a..9e3626e1d4b 100644 --- a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,7 +21,6 @@ import java.io.IOException; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -100,7 +99,6 @@ public class GenericApplicationContext extends AbstractApplicationContext implem */ public GenericApplicationContext() { this.beanFactory = new DefaultListableBeanFactory(); - this.beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); } /** diff --git a/spring-context/src/test/java/org/springframework/context/annotation/LazyAutowiredAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/LazyAutowiredAnnotationBeanPostProcessorTests.java new file mode 100644 index 00000000000..043ed736e78 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/LazyAutowiredAnnotationBeanPostProcessorTests.java @@ -0,0 +1,254 @@ +/* + * Copyright 2002-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.junit.Test; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.tests.sample.beans.TestBean; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + * @since 4.0 + */ +public class LazyAutowiredAnnotationBeanPostProcessorTests { + + private void doTestLazyResourceInjection(Class annotatedBeanClass) { + AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); + RootBeanDefinition abd = new RootBeanDefinition(annotatedBeanClass); + abd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + ac.registerBeanDefinition("annotatedBean", abd); + RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class); + tbd.setLazyInit(true); + ac.registerBeanDefinition("testBean", tbd); + ac.refresh(); + + TestBeanHolder bean = ac.getBean("annotatedBean", TestBeanHolder.class); + assertFalse(ac.getBeanFactory().containsSingleton("testBean")); + assertNotNull(bean.getTestBean()); + assertNull(bean.getTestBean().getName()); + assertTrue(ac.getBeanFactory().containsSingleton("testBean")); + TestBean tb = (TestBean) ac.getBean("testBean"); + tb.setName("tb"); + assertSame("tb", bean.getTestBean().getName()); + } + + @Test + public void testLazyResourceInjectionWithField() { + doTestLazyResourceInjection(FieldResourceInjectionBean.class); + } + + @Test + public void testLazyResourceInjectionWithFieldAndCustomAnnotation() { + doTestLazyResourceInjection(FieldResourceInjectionBeanWithCompositeAnnotation.class); + } + + @Test + public void testLazyResourceInjectionWithMethod() { + doTestLazyResourceInjection(MethodResourceInjectionBean.class); + } + + @Test + public void testLazyResourceInjectionWithMethodLevelLazy() { + doTestLazyResourceInjection(MethodResourceInjectionBeanWithMethodLevelLazy.class); + } + + @Test + public void testLazyResourceInjectionWithMethodAndCustomAnnotation() { + doTestLazyResourceInjection(MethodResourceInjectionBeanWithCompositeAnnotation.class); + } + + @Test + public void testLazyResourceInjectionWithConstructor() { + doTestLazyResourceInjection(ConstructorResourceInjectionBean.class); + } + + @Test + public void testLazyResourceInjectionWithConstructorLevelLazy() { + doTestLazyResourceInjection(ConstructorResourceInjectionBeanWithConstructorLevelLazy.class); + } + + @Test + public void testLazyResourceInjectionWithConstructorAndCustomAnnotation() { + doTestLazyResourceInjection(ConstructorResourceInjectionBeanWithCompositeAnnotation.class); + } + + @Test + public void testLazyResourceInjectionWithNonExistingTarget() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(FieldResourceInjectionBean.class); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + + FieldResourceInjectionBean bean = (FieldResourceInjectionBean) bf.getBean("annotatedBean"); + assertNotNull(bean.getTestBean()); + try { + bean.getTestBean().getName(); + fail("Should have thrown NoSuchBeanDefinitionException"); + } + catch (NoSuchBeanDefinitionException ex) { + // expected; + } + } + + + public interface TestBeanHolder { + + TestBean getTestBean(); + } + + + public static class FieldResourceInjectionBean implements TestBeanHolder { + + @Autowired @Lazy + private TestBean testBean; + + public TestBean getTestBean() { + return this.testBean; + } + } + + + public static class FieldResourceInjectionBeanWithCompositeAnnotation implements TestBeanHolder { + + @LazyInject + private TestBean testBean; + + public TestBean getTestBean() { + return this.testBean; + } + } + + + public static class MethodResourceInjectionBean implements TestBeanHolder { + + private TestBean testBean; + + @Autowired + public void setTestBean(@Lazy TestBean testBean) { + if (this.testBean != null) { + throw new IllegalStateException("Already called"); + } + this.testBean = testBean; + } + + public TestBean getTestBean() { + return this.testBean; + } + } + + + public static class MethodResourceInjectionBeanWithMethodLevelLazy implements TestBeanHolder { + + private TestBean testBean; + + @Autowired @Lazy + public void setTestBean(TestBean testBean) { + if (this.testBean != null) { + throw new IllegalStateException("Already called"); + } + this.testBean = testBean; + } + + public TestBean getTestBean() { + return this.testBean; + } + } + + + public static class MethodResourceInjectionBeanWithCompositeAnnotation implements TestBeanHolder { + + private TestBean testBean; + + @LazyInject + public void setTestBean(TestBean testBean) { + if (this.testBean != null) { + throw new IllegalStateException("Already called"); + } + this.testBean = testBean; + } + + public TestBean getTestBean() { + return this.testBean; + } + } + + + public static class ConstructorResourceInjectionBean implements TestBeanHolder { + + private final TestBean testBean; + + @Autowired + public ConstructorResourceInjectionBean(@Lazy TestBean testBean) { + this.testBean = testBean; + } + + public TestBean getTestBean() { + return this.testBean; + } + } + + + public static class ConstructorResourceInjectionBeanWithConstructorLevelLazy implements TestBeanHolder { + + private final TestBean testBean; + + @Autowired @Lazy + public ConstructorResourceInjectionBeanWithConstructorLevelLazy(TestBean testBean) { + this.testBean = testBean; + } + + public TestBean getTestBean() { + return this.testBean; + } + } + + + public static class ConstructorResourceInjectionBeanWithCompositeAnnotation implements TestBeanHolder { + + private final TestBean testBean; + + @LazyInject + public ConstructorResourceInjectionBeanWithCompositeAnnotation(TestBean testBean) { + this.testBean = testBean; + } + + public TestBean getTestBean() { + return this.testBean; + } + } + + + @Autowired @Lazy + @Retention(RetentionPolicy.RUNTIME) + public @interface LazyInject { + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index 5ed09f139e9..570d749c6f5 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -164,7 +164,7 @@ public class MethodParameter { * Returns the wrapped member. * @return the Method or Constructor as Member */ - private Member getMember() { + public Member getMember() { // NOTE: no ternary expression to retain JDK <8 compatibility even when using // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable // as common type, with that new base class not available on older JDKs) @@ -180,7 +180,7 @@ public class MethodParameter { * Returns the wrapped annotated element. * @return the Method or Constructor as AnnotatedElement */ - private AnnotatedElement getAnnotatedElement() { + public AnnotatedElement getAnnotatedElement() { // NOTE: no ternary expression to retain JDK <8 compatibility even when using // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable // as common type, with that new base class not available on older JDKs) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 5d5f317a681..fbc00e2d8b4 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -55,6 +55,22 @@ public abstract class AnnotationUtils { private static final Map, Boolean> annotatedInterfaceCache = new WeakHashMap, Boolean>(); + /** + * Get a single {@link Annotation} of {@code annotationType} from the supplied + * annotation: either the given annotation itself or a meta-annotation thereof. + * @param ann the Annotation to check + * @param annotationType the annotation class to look for, both locally and as a meta-annotation + * @return the matching annotation or {@code null} if not found + * @since 4.0 + */ + @SuppressWarnings("unchecked") + public static T getAnnotation(Annotation ann, Class annotationType) { + if (annotationType.isInstance(ann)) { + return (T) ann; + } + return ann.annotationType().getAnnotation(annotationType); + } + /** * Get a single {@link Annotation} of {@code annotationType} from the supplied * Method, Constructor or Field. Meta-annotations will be searched if the annotation @@ -98,16 +114,7 @@ public abstract class AnnotationUtils { */ public static A getAnnotation(Method method, Class annotationType) { Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); - A ann = resolvedMethod.getAnnotation(annotationType); - if (ann == null) { - for (Annotation metaAnn : resolvedMethod.getAnnotations()) { - ann = metaAnn.annotationType().getAnnotation(annotationType); - if (ann != null) { - break; - } - } - } - return ann; + return getAnnotation((AnnotatedElement) resolvedMethod, annotationType); } /** diff --git a/spring-web/src/test/java/org/springframework/web/context/support/SpringBeanAutowiringSupportTests.java b/spring-web/src/test/java/org/springframework/web/context/support/SpringBeanAutowiringSupportTests.java index 8412e2b667c..00a5173024f 100644 --- a/spring-web/src/test/java/org/springframework/web/context/support/SpringBeanAutowiringSupportTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/support/SpringBeanAutowiringSupportTests.java @@ -18,12 +18,13 @@ package org.springframework.web.context.support; import org.junit.Test; -import org.springframework.tests.sample.beans.ITestBean; import org.springframework.beans.MutablePropertyValues; -import org.springframework.tests.sample.beans.TestBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.mock.web.test.MockServletContext; +import org.springframework.tests.sample.beans.ITestBean; +import org.springframework.tests.sample.beans.TestBean; import org.springframework.web.context.WebApplicationContext; import static org.junit.Assert.*; @@ -35,14 +36,18 @@ public class SpringBeanAutowiringSupportTests { @Test public void testProcessInjectionBasedOnServletContext() { - MockServletContext sc = new MockServletContext(); StaticWebApplicationContext wac = new StaticWebApplicationContext(); + AnnotationConfigUtils.registerAnnotationConfigProcessors(wac); + MutablePropertyValues pvs = new MutablePropertyValues(); pvs.add("name", "tb"); wac.registerSingleton("testBean", TestBean.class, pvs); + + MockServletContext sc = new MockServletContext(); wac.setServletContext(sc); wac.refresh(); sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac); + InjectionTarget target = new InjectionTarget(); SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(target, sc); assertTrue(target.testBean instanceof TestBean);