Skip to content

Commit 0851766

Browse files
committed
Accept non-generic type match as a fallback
DefaultListableBeanFactory performs a fallback check for autowire candidates now, which GenericTypeAwareAutowireCandidateResolver implements to accept raw type matches if the target class has unresolvable type variables. Full generic matches are still preferred; the BeanFactory will only start looking for fallback matches if the first pass led to an empty result. Issue: SPR-10993 Issue: SPR-11004
1 parent 02f9b71 commit 0851766

File tree

8 files changed

+332
-122
lines changed

8 files changed

+332
-122
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,29 @@ public ResolvableType getResolvableType() {
212212
ResolvableType.forMethodParameter(this.methodParameter));
213213
}
214214

215+
/**
216+
* Return whether a fallback match is allowed.
217+
* <p>This is {@code false} by default but may be overridden to return {@code true} in order
218+
* to suggest to a {@link org.springframework.beans.factory.support.AutowireCandidateResolver}
219+
* that a fallback match is acceptable as well.
220+
*/
221+
public boolean fallbackMatchAllowed() {
222+
return false;
223+
}
224+
225+
/**
226+
* Return a variant of this descriptor that is intended for a fallback match.
227+
* @see #fallbackMatchAllowed()
228+
*/
229+
public DependencyDescriptor forFallbackMatch() {
230+
return new DependencyDescriptor(this) {
231+
@Override
232+
public boolean fallbackMatchAllowed() {
233+
return true;
234+
}
235+
};
236+
}
237+
215238
/**
216239
* Initialize parameter name discovery for the underlying method parameter, if any.
217240
* <p>This method does not actually try to retrieve the parameter name at
@@ -241,7 +264,8 @@ public Class<?> getDependencyType() {
241264
if (this.nestingLevel > 1) {
242265
Type type = this.field.getGenericType();
243266
if (type instanceof ParameterizedType) {
244-
Type arg = ((ParameterizedType) type).getActualTypeArguments()[0];
267+
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
268+
Type arg = args[args.length - 1];
245269
if (arg instanceof Class) {
246270
return (Class) arg;
247271
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,14 @@ protected Map<String, Object> findAutowireCandidates(
979979
result.put(candidateName, getBean(candidateName));
980980
}
981981
}
982+
if (result.isEmpty()) {
983+
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
984+
for (String candidateName : candidateNames) {
985+
if (!candidateName.equals(beanName) && isAutowireCandidate(candidateName, fallbackDescriptor)) {
986+
result.put(candidateName, getBean(candidateName));
987+
}
988+
}
989+
}
982990
return result;
983991
}
984992

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,28 @@ public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDesc
5757
// if explicitly false, do not proceed with any other checks
5858
return false;
5959
}
60-
return (descriptor == null || checkGenericTypeMatch(bdHolder, descriptor.getResolvableType()));
60+
return (descriptor == null || checkGenericTypeMatch(bdHolder, descriptor));
6161
}
6262

6363
/**
6464
* Match the given dependency type with its generic type information
6565
* against the given candidate bean definition.
6666
*/
67-
protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, ResolvableType dependencyType) {
68-
if (dependencyType.getType() instanceof Class) {
67+
protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
68+
ResolvableType dependencyType = descriptor.getResolvableType();
69+
if (!dependencyType.hasGenerics()) {
6970
// No generic type -> we know it's a Class type-match, so no need to check again.
7071
return true;
7172
}
72-
RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();
7373
ResolvableType targetType = null;
74-
if (bd.getResolvedFactoryMethod() != null) {
74+
RootBeanDefinition rbd = null;
75+
if (bdHolder.getBeanDefinition() instanceof RootBeanDefinition) {
76+
rbd = (RootBeanDefinition) bdHolder.getBeanDefinition();
77+
}
78+
if (rbd != null && rbd.getResolvedFactoryMethod() != null) {
7579
// Should typically be set for any kind of factory method, since the BeanFactory
7680
// pre-resolves them before reaching out to the AutowireCandidateResolver...
77-
targetType = ResolvableType.forMethodReturnType(bd.getResolvedFactoryMethod());
81+
targetType = ResolvableType.forMethodReturnType(rbd.getResolvedFactoryMethod());
7882
}
7983
if (targetType == null) {
8084
// Regular case: straight bean instance, with BeanFactory available.
@@ -86,14 +90,20 @@ protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, Resolvabl
8690
}
8791
// Fallback: no BeanFactory set, or no type resolvable through it
8892
// -> best-effort match against the target class if applicable.
89-
if (targetType == null && bd.hasBeanClass() && bd.getFactoryMethodName() == null) {
90-
Class<?> beanClass = bd.getBeanClass();
93+
if (targetType == null && rbd != null && rbd.hasBeanClass() && rbd.getFactoryMethodName() == null) {
94+
Class<?> beanClass = rbd.getBeanClass();
9195
if (!FactoryBean.class.isAssignableFrom(beanClass)) {
9296
targetType = ResolvableType.forClass(ClassUtils.getUserClass(beanClass));
9397
}
9498
}
9599
}
96-
return (targetType == null || dependencyType.isAssignableFrom(targetType));
100+
if (targetType == null) {
101+
return true;
102+
}
103+
if (descriptor.fallbackMatchAllowed() && targetType.hasUnresolvableGenerics()) {
104+
return descriptor.getDependencyType().isAssignableFrom(targetType.getRawClass());
105+
}
106+
return dependencyType.isAssignableFrom(targetType);
97107
}
98108

99109

spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,6 +1305,130 @@ public void testGenericsBasedConstructorInjection() {
13051305
assertSame(ir, bean.integerRepositoryMap.get("integerRepo"));
13061306
}
13071307

1308+
@Test
1309+
public void testGenericsBasedConstructorInjectionWithNonTypedTarget() {
1310+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
1311+
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
1312+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
1313+
bpp.setBeanFactory(bf);
1314+
bf.addBeanPostProcessor(bpp);
1315+
RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class);
1316+
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
1317+
bf.registerBeanDefinition("annotatedBean", bd);
1318+
GenericRepository gr = new GenericRepository();
1319+
bf.registerSingleton("genericRepo", gr);
1320+
1321+
RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean");
1322+
assertSame(gr, bean.stringRepository);
1323+
assertSame(gr, bean.integerRepository);
1324+
assertSame(1, bean.stringRepositoryArray.length);
1325+
assertSame(1, bean.integerRepositoryArray.length);
1326+
assertSame(gr, bean.stringRepositoryArray[0]);
1327+
assertSame(gr, bean.integerRepositoryArray[0]);
1328+
assertSame(1, bean.stringRepositoryList.size());
1329+
assertSame(1, bean.integerRepositoryList.size());
1330+
assertSame(gr, bean.stringRepositoryList.get(0));
1331+
assertSame(gr, bean.integerRepositoryList.get(0));
1332+
assertSame(1, bean.stringRepositoryMap.size());
1333+
assertSame(1, bean.integerRepositoryMap.size());
1334+
assertSame(gr, bean.stringRepositoryMap.get("genericRepo"));
1335+
assertSame(gr, bean.integerRepositoryMap.get("genericRepo"));
1336+
}
1337+
1338+
@Test
1339+
public void testGenericsBasedConstructorInjectionWithNonGenericTarget() {
1340+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
1341+
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
1342+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
1343+
bpp.setBeanFactory(bf);
1344+
bf.addBeanPostProcessor(bpp);
1345+
RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class);
1346+
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
1347+
bf.registerBeanDefinition("annotatedBean", bd);
1348+
SimpleRepository ngr = new SimpleRepository();
1349+
bf.registerSingleton("simpleRepo", ngr);
1350+
1351+
RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean");
1352+
assertSame(ngr, bean.stringRepository);
1353+
assertSame(ngr, bean.integerRepository);
1354+
assertSame(1, bean.stringRepositoryArray.length);
1355+
assertSame(1, bean.integerRepositoryArray.length);
1356+
assertSame(ngr, bean.stringRepositoryArray[0]);
1357+
assertSame(ngr, bean.integerRepositoryArray[0]);
1358+
assertSame(1, bean.stringRepositoryList.size());
1359+
assertSame(1, bean.integerRepositoryList.size());
1360+
assertSame(ngr, bean.stringRepositoryList.get(0));
1361+
assertSame(ngr, bean.integerRepositoryList.get(0));
1362+
assertSame(1, bean.stringRepositoryMap.size());
1363+
assertSame(1, bean.integerRepositoryMap.size());
1364+
assertSame(ngr, bean.stringRepositoryMap.get("simpleRepo"));
1365+
assertSame(ngr, bean.integerRepositoryMap.get("simpleRepo"));
1366+
}
1367+
1368+
@Test
1369+
public void testGenericsBasedConstructorInjectionWithMixedTargets() {
1370+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
1371+
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
1372+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
1373+
bpp.setBeanFactory(bf);
1374+
bf.addBeanPostProcessor(bpp);
1375+
RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class);
1376+
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
1377+
bf.registerBeanDefinition("annotatedBean", bd);
1378+
StringRepository sr = new StringRepository();
1379+
bf.registerSingleton("stringRepo", sr);
1380+
GenericRepository gr = new GenericRepositorySubclass();
1381+
bf.registerSingleton("genericRepo", gr);
1382+
1383+
RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean");
1384+
assertSame(sr, bean.stringRepository);
1385+
assertSame(gr, bean.integerRepository);
1386+
assertSame(1, bean.stringRepositoryArray.length);
1387+
assertSame(1, bean.integerRepositoryArray.length);
1388+
assertSame(sr, bean.stringRepositoryArray[0]);
1389+
assertSame(gr, bean.integerRepositoryArray[0]);
1390+
assertSame(1, bean.stringRepositoryList.size());
1391+
assertSame(1, bean.integerRepositoryList.size());
1392+
assertSame(sr, bean.stringRepositoryList.get(0));
1393+
assertSame(gr, bean.integerRepositoryList.get(0));
1394+
assertSame(1, bean.stringRepositoryMap.size());
1395+
assertSame(1, bean.integerRepositoryMap.size());
1396+
assertSame(sr, bean.stringRepositoryMap.get("stringRepo"));
1397+
assertSame(gr, bean.integerRepositoryMap.get("genericRepo"));
1398+
}
1399+
1400+
@Test
1401+
public void testGenericsBasedConstructorInjectionWithMixedTargetsIncludingNonGeneric() {
1402+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
1403+
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
1404+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
1405+
bpp.setBeanFactory(bf);
1406+
bf.addBeanPostProcessor(bpp);
1407+
RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class);
1408+
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
1409+
bf.registerBeanDefinition("annotatedBean", bd);
1410+
StringRepository sr = new StringRepository();
1411+
bf.registerSingleton("stringRepo", sr);
1412+
SimpleRepository ngr = new SimpleRepositorySubclass();
1413+
bf.registerSingleton("simpleRepo", ngr);
1414+
1415+
RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean");
1416+
assertSame(sr, bean.stringRepository);
1417+
assertSame(ngr, bean.integerRepository);
1418+
assertSame(1, bean.stringRepositoryArray.length);
1419+
assertSame(1, bean.integerRepositoryArray.length);
1420+
assertSame(sr, bean.stringRepositoryArray[0]);
1421+
assertSame(ngr, bean.integerRepositoryArray[0]);
1422+
assertSame(1, bean.stringRepositoryList.size());
1423+
assertSame(1, bean.integerRepositoryList.size());
1424+
assertSame(sr, bean.stringRepositoryList.get(0));
1425+
assertSame(ngr, bean.integerRepositoryList.get(0));
1426+
assertSame(1, bean.stringRepositoryMap.size());
1427+
assertSame(1, bean.integerRepositoryMap.size());
1428+
assertSame(sr, bean.stringRepositoryMap.get("stringRepo"));
1429+
assertSame(ngr, bean.integerRepositoryMap.get("simpleRepo"));
1430+
}
1431+
13081432

13091433
public static class ResourceInjectionBean {
13101434

@@ -1859,6 +1983,18 @@ public static class StringRepository implements Repository<String> {
18591983
public static class IntegerRepository implements Repository<Integer> {
18601984
}
18611985

1986+
public static class GenericRepository<T> implements Repository<T> {
1987+
}
1988+
1989+
public static class GenericRepositorySubclass extends GenericRepository {
1990+
}
1991+
1992+
public static class SimpleRepository implements Repository {
1993+
}
1994+
1995+
public static class SimpleRepositorySubclass extends SimpleRepository {
1996+
}
1997+
18621998

18631999
public static class RepositoryFieldInjectionBean {
18642000

spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -251,27 +251,13 @@ public static Class[] resolveTypeArguments(Class<?> clazz, Class<?> genericIfc)
251251
/**
252252
* Resolve the specified generic type against the given TypeVariable map.
253253
* @param genericType the generic type to resolve
254-
* @param typeVariableMap the TypeVariable Map to resolved against
254+
* @param map the TypeVariable Map to resolved against
255255
* @return the type if it resolves to a Class, or {@code Object.class} otherwise
256256
* @deprecated as of Spring 4.0 in favor of {@link ResolvableType}
257257
*/
258258
@Deprecated
259-
public static Class<?> resolveType(Type genericType, final Map<TypeVariable, Type> typeVariableMap) {
260-
261-
ResolvableType.VariableResolver variableResolver = new ResolvableType.VariableResolver() {
262-
@Override
263-
public ResolvableType resolveVariable(TypeVariable<?> variable) {
264-
Type type = typeVariableMap.get(variable);
265-
return (type == null ? null : ResolvableType.forType(type));
266-
}
267-
268-
@Override
269-
public Object getSource() {
270-
return typeVariableMap;
271-
}
272-
};
273-
274-
return ResolvableType.forType(genericType, variableResolver).resolve(Object.class);
259+
public static Class<?> resolveType(Type genericType, Map<TypeVariable, Type> map) {
260+
return ResolvableType.forType(genericType, new TypeVariableMapVariableResolver(map)).resolve(Object.class);
275261
}
276262

277263
/**
@@ -315,4 +301,26 @@ private static void buildTypeVariableMap(ResolvableType type, Map<TypeVariable,
315301
}
316302
}
317303

304+
305+
@SuppressWarnings("serial")
306+
private static class TypeVariableMapVariableResolver implements ResolvableType.VariableResolver {
307+
308+
private final Map<TypeVariable, Type> typeVariableMap;
309+
310+
public TypeVariableMapVariableResolver(Map<TypeVariable, Type> typeVariableMap) {
311+
this.typeVariableMap = typeVariableMap;
312+
}
313+
314+
@Override
315+
public ResolvableType resolveVariable(TypeVariable<?> variable) {
316+
Type type = this.typeVariableMap.get(variable);
317+
return (type != null ? ResolvableType.forType(type) : null);
318+
}
319+
320+
@Override
321+
public Object getSource() {
322+
return this.typeVariableMap;
323+
}
324+
}
325+
318326
}

spring-core/src/main/java/org/springframework/core/MethodParameter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@ public Class<?> getNestedParameterType() {
265265
Type type = getGenericParameterType();
266266
if (type instanceof ParameterizedType) {
267267
Integer index = getTypeIndexForCurrentLevel();
268-
Type arg = ((ParameterizedType) type).getActualTypeArguments()[index != null ? index : 0];
268+
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
269+
Type arg = args[index != null ? index : args.length - 1];
269270
if (arg instanceof Class) {
270271
return (Class) arg;
271272
}

0 commit comments

Comments
 (0)