Skip to content

Commit 4a85309

Browse files
committed
Consistent throwing of BeanNotOfRequiredTypeException even for existing proxy
Issue: SPR-14504
1 parent 2a3e62d commit 4a85309

File tree

2 files changed

+77
-5
lines changed

2 files changed

+77
-5
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.springframework.beans.factory.BeanFactory;
5353
import org.springframework.beans.factory.BeanFactoryAware;
5454
import org.springframework.beans.factory.BeanFactoryUtils;
55+
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
5556
import org.springframework.beans.factory.CannotLoadBeanClassException;
5657
import org.springframework.beans.factory.FactoryBean;
5758
import org.springframework.beans.factory.InjectionPoint;
@@ -1043,7 +1044,7 @@ public Object doResolveDependency(DependencyDescriptor descriptor, String beanNa
10431044
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
10441045
if (matchingBeans.isEmpty()) {
10451046
if (descriptor.isRequired()) {
1046-
raiseNoSuchBeanDefinitionException(type, descriptor.getResolvableType().toString(), descriptor);
1047+
raiseNoMatchingBeanFound(type, descriptor.getResolvableType().toString(), descriptor);
10471048
}
10481049
return null;
10491050
}
@@ -1382,17 +1383,46 @@ private boolean isSelfReference(String beanName, String candidateName) {
13821383
}
13831384

13841385
/**
1385-
* Raise a NoSuchBeanDefinitionException for an unresolvable dependency.
1386+
* Raise a NoSuchBeanDefinitionException or BeanNotOfRequiredTypeException
1387+
* for an unresolvable dependency.
13861388
*/
1387-
private void raiseNoSuchBeanDefinitionException(
1388-
Class<?> type, String dependencyDescription, DependencyDescriptor descriptor)
1389-
throws NoSuchBeanDefinitionException {
1389+
private void raiseNoMatchingBeanFound(
1390+
Class<?> type, String dependencyDescription, DependencyDescriptor descriptor) throws BeansException {
1391+
1392+
checkBeanNotOfRequiredType(type, descriptor);
13901393

13911394
throw new NoSuchBeanDefinitionException(type, dependencyDescription,
13921395
"expected at least 1 bean which qualifies as autowire candidate for this dependency. " +
13931396
"Dependency annotations: " + ObjectUtils.nullSafeToString(descriptor.getAnnotations()));
13941397
}
13951398

1399+
/**
1400+
* Raise a BeanNotOfRequiredTypeException for an unresolvable dependency, if applicable,
1401+
* i.e. if the target type of the bean would match but an exposed proxy doesn't.
1402+
*/
1403+
private void checkBeanNotOfRequiredType(Class<?> type, DependencyDescriptor descriptor) {
1404+
for (String beanName : this.beanDefinitionNames) {
1405+
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
1406+
Class<?> targetType = mbd.getTargetType();
1407+
if (targetType != null && type.isAssignableFrom(targetType) &&
1408+
isAutowireCandidate(beanName, mbd, descriptor, getAutowireCandidateResolver())) {
1409+
// Probably a poxy interfering with target type match -> throw meaningful exception.
1410+
Object beanInstance = getSingleton(beanName, false);
1411+
Class<?> beanType = (beanInstance != null ? beanInstance.getClass() : predictBeanType(beanName, mbd));
1412+
if (type != beanType) {
1413+
throw new BeanNotOfRequiredTypeException(beanName, type, beanType);
1414+
}
1415+
}
1416+
}
1417+
1418+
if (getParentBeanFactory() instanceof DefaultListableBeanFactory) {
1419+
((DefaultListableBeanFactory) getParentBeanFactory()).checkBeanNotOfRequiredType(type, descriptor);
1420+
}
1421+
}
1422+
1423+
/**
1424+
* Create an {@link Optional} wrapper for the specified dependency.
1425+
*/
13961426
private Optional<?> createOptionalDependency(DependencyDescriptor descriptor, String beanName, final Object... args) {
13971427
DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) {
13981428
@Override

spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
3434
import org.springframework.aop.support.AopUtils;
3535
import org.springframework.beans.factory.BeanDefinitionStoreException;
36+
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
37+
import org.springframework.beans.factory.UnsatisfiedDependencyException;
3638
import org.springframework.beans.factory.annotation.Qualifier;
3739
import org.springframework.context.annotation.AdviceMode;
3840
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -41,6 +43,7 @@
4143
import org.springframework.context.annotation.Lazy;
4244
import org.springframework.core.Ordered;
4345
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
46+
import org.springframework.stereotype.Component;
4447
import org.springframework.util.ReflectionUtils;
4548

4649
import static org.hamcrest.CoreMatchers.anyOf;
@@ -81,6 +84,36 @@ public void proxyingOccursWithMockitoStub() {
8184
asyncBean.work();
8285
}
8386

87+
@Test
88+
public void properExceptionForExistingProxyDependencyMismatch() {
89+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
90+
ctx.register(AsyncConfig.class, AsyncBeanWithInterface.class, AsyncBeanUser.class);
91+
92+
try {
93+
ctx.refresh();
94+
fail("Should have thrown UnsatisfiedDependencyException");
95+
}
96+
catch (UnsatisfiedDependencyException ex) {
97+
ex.printStackTrace();
98+
assertTrue(ex.getCause() instanceof BeanNotOfRequiredTypeException);
99+
}
100+
}
101+
102+
@Test
103+
public void properExceptionForResolvedProxyDependencyMismatch() {
104+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
105+
ctx.register(AsyncConfig.class, AsyncBeanUser.class, AsyncBeanWithInterface.class);
106+
107+
try {
108+
ctx.refresh();
109+
fail("Should have thrown UnsatisfiedDependencyException");
110+
}
111+
catch (UnsatisfiedDependencyException ex) {
112+
ex.printStackTrace();
113+
assertTrue(ex.getCause() instanceof BeanNotOfRequiredTypeException);
114+
}
115+
}
116+
84117
@Test
85118
public void withAsyncBeanWithExecutorQualifiedByName() throws ExecutionException, InterruptedException {
86119
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
@@ -214,6 +247,15 @@ public Thread getThreadOfExecution() {
214247
}
215248

216249

250+
@Component("asyncBean")
251+
static class AsyncBeanWithInterface extends AsyncBean implements Runnable {
252+
253+
@Override
254+
public void run() {
255+
}
256+
}
257+
258+
217259
static class AsyncBeanUser {
218260

219261
private final AsyncBean asyncBean;

0 commit comments

Comments
 (0)