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 1ff07eaa6db..bb4ef59bf86 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 @@ -52,6 +52,7 @@ import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.BeanNotOfRequiredTypeException; import org.springframework.beans.factory.CannotLoadBeanClassException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InjectionPoint; @@ -1053,7 +1054,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto Map matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { - raiseNoSuchBeanDefinitionException(type, descriptor.getResolvableType().toString(), descriptor); + raiseNoMatchingBeanFound(type, descriptor.getResolvableType().toString(), descriptor); } return null; } @@ -1392,17 +1393,43 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } /** - * Raise a NoSuchBeanDefinitionException for an unresolvable dependency. + * Raise a NoSuchBeanDefinitionException or BeanNotOfRequiredTypeException + * for an unresolvable dependency. */ - private void raiseNoSuchBeanDefinitionException( - Class type, String dependencyDescription, DependencyDescriptor descriptor) - throws NoSuchBeanDefinitionException { + private void raiseNoMatchingBeanFound( + Class type, String dependencyDescription, DependencyDescriptor descriptor) throws BeansException { + + checkBeanNotOfRequiredType(type, descriptor); throw new NoSuchBeanDefinitionException(type, dependencyDescription, "expected at least 1 bean which qualifies as autowire candidate for this dependency. " + "Dependency annotations: " + ObjectUtils.nullSafeToString(descriptor.getAnnotations())); } + /** + * Raise a BeanNotOfRequiredTypeException for an unresolvable dependency, if applicable, + * i.e. if the target type of the bean would match but an exposed proxy doesn't. + */ + private void checkBeanNotOfRequiredType(Class type, DependencyDescriptor descriptor) { + for (String beanName : this.beanDefinitionNames) { + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + Class targetType = mbd.getTargetType(); + if (targetType != null && type.isAssignableFrom(targetType) && + isAutowireCandidate(beanName, mbd, descriptor, getAutowireCandidateResolver())) { + // Probably a poxy interfering with target type match -> throw meaningful exception. + Object beanInstance = getSingleton(beanName, false); + Class beanType = (beanInstance != null ? beanInstance.getClass() : predictBeanType(beanName, mbd)); + if (type != beanType) { + throw new BeanNotOfRequiredTypeException(beanName, type, beanType); + } + } + } + + if (getParentBeanFactory() instanceof DefaultListableBeanFactory) { + ((DefaultListableBeanFactory) getParentBeanFactory()).checkBeanNotOfRequiredType(type, descriptor); + } + } + @Override public String toString() { diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java index 0a92dedb887..5fa835f40d9 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java @@ -33,6 +33,8 @@ import org.springframework.aop.framework.Advised; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanNotOfRequiredTypeException; +import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -41,6 +43,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.core.Ordered; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Component; import org.springframework.util.ReflectionUtils; import static org.hamcrest.CoreMatchers.anyOf; @@ -81,6 +84,36 @@ public class EnableAsyncTests { asyncBean.work(); } + @Test + public void properExceptionForExistingProxyDependencyMismatch() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(AsyncConfig.class, AsyncBeanWithInterface.class, AsyncBeanUser.class); + + try { + ctx.refresh(); + fail("Should have thrown UnsatisfiedDependencyException"); + } + catch (UnsatisfiedDependencyException ex) { + ex.printStackTrace(); + assertTrue(ex.getCause() instanceof BeanNotOfRequiredTypeException); + } + } + + @Test + public void properExceptionForResolvedProxyDependencyMismatch() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(AsyncConfig.class, AsyncBeanUser.class, AsyncBeanWithInterface.class); + + try { + ctx.refresh(); + fail("Should have thrown UnsatisfiedDependencyException"); + } + catch (UnsatisfiedDependencyException ex) { + ex.printStackTrace(); + assertTrue(ex.getCause() instanceof BeanNotOfRequiredTypeException); + } + } + @Test public void withAsyncBeanWithExecutorQualifiedByName() throws ExecutionException, InterruptedException { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); @@ -214,6 +247,15 @@ public class EnableAsyncTests { } + @Component("asyncBean") + static class AsyncBeanWithInterface extends AsyncBean implements Runnable { + + @Override + public void run() { + } + } + + static class AsyncBeanUser { private final AsyncBean asyncBean;