From 4b06b60007bc737681457ac7184f070b1162f51a Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 17 Aug 2016 16:59:16 +0200 Subject: [PATCH] RootBeanDefinition accepts ResolvableType for target type hint Issue: SPR-14580 --- .../AbstractAutowireCapableBeanFactory.java | 5 +- ...ricTypeAwareAutowireCandidateResolver.java | 31 +++++++++--- .../factory/support/RootBeanDefinition.java | 37 ++++++++++---- ...wiredAnnotationBeanPostProcessorTests.java | 50 +++++++++++++++++++ 4 files changed, 105 insertions(+), 18 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 6ac445f1691..b7871d7a6a0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -624,10 +624,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac protected Class determineTargetType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { Class targetType = mbd.getTargetType(); if (targetType == null) { - targetType = (mbd.getFactoryMethodName() != null ? getTypeForFactoryMethod(beanName, mbd, typesToMatch) : + targetType = (mbd.getFactoryMethodName() != null ? + getTypeForFactoryMethod(beanName, mbd, typesToMatch) : resolveBeanClass(mbd, beanName, typesToMatch)); if (ObjectUtils.isEmpty(typesToMatch) || getTempClassLoader() == null) { - mbd.setTargetType(targetType); + mbd.resolvedTargetType = targetType; } } return targetType; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java index 65554ce9b57..cba9e9770ab 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -74,21 +74,31 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid // No generic type -> we know it's a Class type-match, so no need to check again. return true; } + ResolvableType targetType = null; + boolean cacheType = false; RootBeanDefinition rbd = null; if (bdHolder.getBeanDefinition() instanceof RootBeanDefinition) { rbd = (RootBeanDefinition) bdHolder.getBeanDefinition(); } if (rbd != null) { - // First, check factory method return type, if applicable - targetType = getReturnTypeForFactoryMethod(rbd, descriptor); + targetType = rbd.targetType; if (targetType == null) { - RootBeanDefinition dbd = getResolvedDecoratedDefinition(rbd); - if (dbd != null) { - targetType = getReturnTypeForFactoryMethod(dbd, descriptor); + cacheType = true; + // First, check factory method return type, if applicable + targetType = getReturnTypeForFactoryMethod(rbd, descriptor); + if (targetType == null) { + RootBeanDefinition dbd = getResolvedDecoratedDefinition(rbd); + if (dbd != null) { + targetType = dbd.targetType; + if (targetType == null) { + targetType = getReturnTypeForFactoryMethod(dbd, descriptor); + } + } } } } + if (targetType == null) { // Regular case: straight bean instance, with BeanFactory available. if (this.beanFactory != null) { @@ -106,7 +116,14 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid } } } - if (targetType == null || (descriptor.fallbackMatchAllowed() && targetType.hasUnresolvableGenerics())) { + + if (targetType == null) { + return true; + } + if (cacheType) { + rbd.targetType = targetType; + } + if (descriptor.fallbackMatchAllowed() && targetType.hasUnresolvableGenerics()) { return true; } // Full check for complex generic type match... diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index 11ee1845e51..a8f117c20da 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -26,6 +26,7 @@ import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.core.ResolvableType; import org.springframework.util.Assert; /** @@ -49,22 +50,26 @@ import org.springframework.util.Assert; @SuppressWarnings("serial") public class RootBeanDefinition extends AbstractBeanDefinition { - boolean allowCaching = true; - private BeanDefinitionHolder decoratedDefinition; - private volatile Class targetType; + boolean allowCaching = true; + + volatile ResolvableType targetType; boolean isFactoryMethodUnique = false; + /** Package-visible field for caching the determined Class of a given bean definition */ + volatile Class resolvedTargetType; + + /** Package-visible field for caching the return type of a generically typed factory method */ + volatile Class resolvedFactoryMethodReturnType; + + /** Common lock for the four constructor fields below */ final Object constructorArgumentLock = new Object(); /** Package-visible field for caching the resolved constructor or factory method */ Executable resolvedConstructorOrFactoryMethod; - /** Package-visible field for caching the return type of a generically typed factory method */ - volatile Class resolvedFactoryMethodReturnType; - /** Package-visible field that marks the constructor arguments as resolved */ boolean constructorArgumentsResolved = false; @@ -74,6 +79,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { /** Package-visible field for caching partly prepared constructor arguments */ Object[] preparedConstructorArguments; + /** Common lock for the two post-processing fields below */ final Object postProcessingLock = new Object(); /** Package-visible field that indicates MergedBeanDefinitionPostProcessor having been applied */ @@ -172,8 +178,8 @@ public class RootBeanDefinition extends AbstractBeanDefinition { */ public RootBeanDefinition(RootBeanDefinition original) { super(original); - this.allowCaching = original.allowCaching; this.decoratedDefinition = original.decoratedDefinition; + this.allowCaching = original.allowCaching; this.targetType = original.targetType; this.isFactoryMethodUnique = original.isFactoryMethodUnique; } @@ -214,19 +220,32 @@ public class RootBeanDefinition extends AbstractBeanDefinition { return this.decoratedDefinition; } + /** + * Specify a generics-containing target type of this bean definition, if known in advance. + * @since 4.3.3 + */ + public void setTargetType(ResolvableType targetType) { + this.targetType = targetType; + } + /** * Specify the target type of this bean definition, if known in advance. + * @since 3.2.2 */ public void setTargetType(Class targetType) { - this.targetType = targetType; + this.targetType = (targetType != null ? ResolvableType.forClass(targetType) : null); } /** * Return the target type of this bean definition, if known * (either specified in advance or resolved on first instantiation). + * @since 3.2.2 */ public Class getTargetType() { - return this.targetType; + if (this.resolvedTargetType != null) { + return this.resolvedTargetType; + } + return (this.targetType != null ? this.targetType.resolve() : 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 c437650945c..090d395833d 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 @@ -41,6 +41,7 @@ import org.mockito.Mockito; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; @@ -2047,6 +2048,24 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertSame(bean2, bean1.gi2); } + @Test + public void testGenericsBasedInjectionWithBeanDefinitionTargetResolvableType() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd1 = new RootBeanDefinition(GenericInterface2Bean.class); + bd1.setTargetType(ResolvableType.forClassWithGenerics(GenericInterface2Bean.class, String.class)); + bf.registerBeanDefinition("bean1", bd1); + RootBeanDefinition bd2 = new RootBeanDefinition(GenericInterface2Bean.class); + bd2.setTargetType(ResolvableType.forClassWithGenerics(GenericInterface2Bean.class, Integer.class)); + bf.registerBeanDefinition("bean2", bd2); + bf.registerBeanDefinition("bean3", new RootBeanDefinition(MultiGenericFieldInjection.class)); + + assertEquals("bean1 a bean2 123", bf.getBean("bean3").toString()); + } + @Test public void testCircularTypeReference() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -3139,6 +3158,37 @@ public class AutowiredAnnotationBeanPostProcessorTests { } + public static class GenericInterface2Bean implements GenericInterface2, BeanNameAware { + + private String name; + + @Override + public void setBeanName(String name) { + this.name = name; + } + + @Override + public String doSomethingMoreGeneric(K o) { + return this.name + " " + o; + } + } + + + public static class MultiGenericFieldInjection { + + @Autowired + private GenericInterface2 stringBean; + + @Autowired + private GenericInterface2 integerBean; + + @Override + public String toString() { + return this.stringBean.doSomethingMoreGeneric("a") + " " + this.integerBean.doSomethingMoreGeneric(123); + } + } + + @SuppressWarnings("rawtypes") public static class PlainGenericInterface2Impl implements GenericInterface2 {