Skip to content

Commit 778a019

Browse files
committed
ResolvableType-based type matching at the BeanFactory API level
Issue: SPR-12147
1 parent a3e5fbf commit 778a019

File tree

14 files changed

+323
-102
lines changed

14 files changed

+323
-102
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.beans.factory;
1818

1919
import org.springframework.beans.BeansException;
20+
import org.springframework.core.ResolvableType;
2021

2122
/**
2223
* The root interface for accessing a Spring bean container.
@@ -114,6 +115,7 @@ public interface BeanFactory {
114115
*/
115116
String FACTORY_BEAN_PREFIX = "&";
116117

118+
117119
/**
118120
* Return an instance, which may be shared or independent, of the specified bean.
119121
* <p>This method allows a Spring BeanFactory to be used as a replacement for the
@@ -202,6 +204,7 @@ public interface BeanFactory {
202204
*/
203205
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
204206

207+
205208
/**
206209
* Does this bean factory contain a bean definition or externally registered singleton
207210
* instance with the given name?
@@ -261,7 +264,24 @@ public interface BeanFactory {
261264
* <p>Translates aliases back to the corresponding canonical bean name.
262265
* Will ask the parent factory if the bean cannot be found in this factory instance.
263266
* @param name the name of the bean to query
264-
* @param targetType the type to match against
267+
* @param targetType the type to match against (as a {@code ResolvableType})
268+
* @return {@code true} if the bean type matches,
269+
* {@code false} if it doesn't match or cannot be determined yet
270+
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
271+
* @since 4.2
272+
* @see #getBean
273+
* @see #getType
274+
*/
275+
boolean isTypeMatch(String name, ResolvableType targetType) throws NoSuchBeanDefinitionException;
276+
277+
/**
278+
* Check whether the bean with the given name matches the specified type.
279+
* More specifically, check whether a {@link #getBean} call for the given name
280+
* would return an object that is assignable to the specified target type.
281+
* <p>Translates aliases back to the corresponding canonical bean name.
282+
* Will ask the parent factory if the bean cannot be found in this factory instance.
283+
* @param name the name of the bean to query
284+
* @param targetType the type to match against (as a {@code Class})
265285
* @return {@code true} if the bean type matches,
266286
* {@code false} if it doesn't match or cannot be determined yet
267287
* @throws NoSuchBeanDefinitionException if there is no bean with the given name

spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,14 +16,14 @@
1616

1717
package org.springframework.beans.factory;
1818

19-
2019
import java.util.ArrayList;
2120
import java.util.Arrays;
2221
import java.util.LinkedHashMap;
2322
import java.util.List;
2423
import java.util.Map;
2524

2625
import org.springframework.beans.BeansException;
26+
import org.springframework.core.ResolvableType;
2727
import org.springframework.util.Assert;
2828
import org.springframework.util.StringUtils;
2929

@@ -126,6 +126,39 @@ public static String[] beanNamesIncludingAncestors(ListableBeanFactory lbf) {
126126
return beanNamesForTypeIncludingAncestors(lbf, Object.class);
127127
}
128128

129+
/**
130+
* Get all bean names for the given type, including those defined in ancestor
131+
* factories. Will return unique names in case of overridden bean definitions.
132+
* <p>Does consider objects created by FactoryBeans, which means that FactoryBeans
133+
* will get initialized. If the object created by the FactoryBean doesn't match,
134+
* the raw FactoryBean itself will be matched against the type.
135+
* <p>This version of {@code beanNamesForTypeIncludingAncestors} automatically
136+
* includes prototypes and FactoryBeans.
137+
* @param lbf the bean factory
138+
* @param type the type that beans must match (as a {@code ResolvableType})
139+
* @return the array of matching bean names, or an empty array if none
140+
* @since 4.2
141+
*/
142+
public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, ResolvableType type) {
143+
Assert.notNull(lbf, "ListableBeanFactory must not be null");
144+
String[] result = lbf.getBeanNamesForType(type);
145+
if (lbf instanceof HierarchicalBeanFactory) {
146+
HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
147+
if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
148+
String[] parentResult = beanNamesForTypeIncludingAncestors(
149+
(ListableBeanFactory) hbf.getParentBeanFactory(), type);
150+
List<String> resultList = new ArrayList<String>();
151+
resultList.addAll(Arrays.asList(result));
152+
for (String beanName : parentResult) {
153+
if (!resultList.contains(beanName) && !hbf.containsLocalBean(beanName)) {
154+
resultList.add(beanName);
155+
}
156+
}
157+
result = StringUtils.toStringArray(resultList);
158+
}
159+
}
160+
return result;
161+
}
129162

130163
/**
131164
* Get all bean names for the given type, including those defined in ancestor
@@ -136,7 +169,7 @@ public static String[] beanNamesIncludingAncestors(ListableBeanFactory lbf) {
136169
* <p>This version of {@code beanNamesForTypeIncludingAncestors} automatically
137170
* includes prototypes and FactoryBeans.
138171
* @param lbf the bean factory
139-
* @param type the type that beans must match
172+
* @param type the type that beans must match (as a {@code Class})
140173
* @return the array of matching bean names, or an empty array if none
141174
*/
142175
public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, Class<?> type) {

spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.util.Map;
2121

2222
import org.springframework.beans.BeansException;
23+
import org.springframework.core.ResolvableType;
2324

2425
/**
2526
* Extension of the {@link BeanFactory} interface to be implemented by bean factories
@@ -85,6 +86,34 @@ public interface ListableBeanFactory extends BeanFactory {
8586
*/
8687
String[] getBeanDefinitionNames();
8788

89+
/**
90+
* Return the names of beans matching the given type (including subclasses),
91+
* judging from either bean definitions or the value of {@code getObjectType}
92+
* in the case of FactoryBeans.
93+
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
94+
* check nested beans which might match the specified type as well.
95+
* <p>Does consider objects created by FactoryBeans, which means that FactoryBeans
96+
* will get initialized. If the object created by the FactoryBean doesn't match,
97+
* the raw FactoryBean itself will be matched against the type.
98+
* <p>Does not consider any hierarchy this factory may participate in.
99+
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
100+
* to include beans in ancestor factories too.
101+
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
102+
* by other means than bean definitions.
103+
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
104+
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
105+
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
106+
* <p>Bean names returned by this method should always return bean names <i>in the
107+
* order of definition</i> in the backend configuration, as far as possible.
108+
* @param type the class or interface to match, or {@code null} for all bean names
109+
* @return the names of beans (or objects created by FactoryBeans) matching
110+
* the given object type (including subclasses), or an empty array if none
111+
* @since 4.2
112+
* @see FactoryBean#getObjectType
113+
* @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, ResolvableType)
114+
*/
115+
String[] getBeanNamesForType(ResolvableType type);
116+
88117
/**
89118
* Return the names of beans matching the given type (including subclasses),
90119
* judging from either bean definitions or the value of {@code getObjectType}

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import org.springframework.beans.factory.config.Scope;
6969
import org.springframework.core.DecoratingClassLoader;
7070
import org.springframework.core.NamedThreadLocal;
71+
import org.springframework.core.ResolvableType;
7172
import org.springframework.core.convert.ConversionService;
7273
import org.springframework.util.Assert;
7374
import org.springframework.util.ClassUtils;
@@ -479,25 +480,23 @@ public Boolean run() {
479480
}
480481

481482
@Override
482-
public boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException {
483+
public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
483484
String beanName = transformedBeanName(name);
484-
Class<?> typeToMatch = (targetType != null ? targetType : Object.class);
485485

486486
// Check manually registered singletons.
487487
Object beanInstance = getSingleton(beanName, false);
488488
if (beanInstance != null) {
489489
if (beanInstance instanceof FactoryBean) {
490490
if (!BeanFactoryUtils.isFactoryDereference(name)) {
491491
Class<?> type = getTypeForFactoryBean((FactoryBean<?>) beanInstance);
492-
return (type != null && ClassUtils.isAssignable(typeToMatch, type));
492+
return (type != null && typeToMatch.isAssignableFrom(type));
493493
}
494494
else {
495-
return ClassUtils.isAssignableValue(typeToMatch, beanInstance);
495+
return typeToMatch.isInstance(beanInstance);
496496
}
497497
}
498498
else {
499-
return !BeanFactoryUtils.isFactoryDereference(name) &&
500-
ClassUtils.isAssignableValue(typeToMatch, beanInstance);
499+
return (!BeanFactoryUtils.isFactoryDereference(name) && typeToMatch.isInstance(beanInstance));
501500
}
502501
}
503502
else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) {
@@ -510,14 +509,15 @@ else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) {
510509
BeanFactory parentBeanFactory = getParentBeanFactory();
511510
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
512511
// No bean definition found in this factory -> delegate to parent.
513-
return parentBeanFactory.isTypeMatch(originalBeanName(name), targetType);
512+
return parentBeanFactory.isTypeMatch(originalBeanName(name), typeToMatch);
514513
}
515514

516515
// Retrieve corresponding bean definition.
517516
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
518517

519-
Class<?>[] typesToMatch = (FactoryBean.class.equals(typeToMatch) ?
520-
new Class<?>[] {typeToMatch} : new Class<?>[] {FactoryBean.class, typeToMatch});
518+
Class<?> classToMatch = typeToMatch.getRawClass();
519+
Class<?>[] typesToMatch = (FactoryBean.class.equals(classToMatch) ?
520+
new Class<?>[] {classToMatch} : new Class<?>[] {FactoryBean.class, classToMatch});
521521

522522
// Check decorated bean definition, if any: We assume it'll be easier
523523
// to determine the decorated bean's type than the proxy's type.
@@ -559,6 +559,11 @@ else if (BeanFactoryUtils.isFactoryDereference(name)) {
559559
}
560560
}
561561

562+
@Override
563+
public boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException {
564+
return isTypeMatch(name, ResolvableType.forClass(targetType));
565+
}
566+
562567
@Override
563568
public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
564569
String beanName = transformedBeanName(name);

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
6767
import org.springframework.beans.factory.config.DependencyDescriptor;
6868
import org.springframework.core.OrderComparator;
69+
import org.springframework.core.ResolvableType;
6970
import org.springframework.core.annotation.AnnotationUtils;
7071
import org.springframework.lang.UsesJava8;
7172
import org.springframework.util.Assert;
@@ -323,7 +324,7 @@ public void copyConfigurationFrom(ConfigurableBeanFactory otherFactory) {
323324

324325

325326
//---------------------------------------------------------------------
326-
// Implementation of ListableBeanFactory interface
327+
// Implementation of remaining BeanFactory methods
327328
//---------------------------------------------------------------------
328329

329330
@Override
@@ -372,6 +373,11 @@ else if (getParentBeanFactory() != null) {
372373
}
373374
}
374375

376+
377+
//---------------------------------------------------------------------
378+
// Implementation of ListableBeanFactory interface
379+
//---------------------------------------------------------------------
380+
375381
@Override
376382
public boolean containsBeanDefinition(String beanName) {
377383
Assert.notNull(beanName, "Bean name must not be null");
@@ -393,6 +399,11 @@ public String[] getBeanDefinitionNames() {
393399
}
394400
}
395401

402+
@Override
403+
public String[] getBeanNamesForType(ResolvableType type) {
404+
return doGetBeanNamesForType(type, true, true);
405+
}
406+
396407
@Override
397408
public String[] getBeanNamesForType(Class<?> type) {
398409
return getBeanNamesForType(type, true, true);
@@ -401,22 +412,23 @@ public String[] getBeanNamesForType(Class<?> type) {
401412
@Override
402413
public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
403414
if (!isConfigurationFrozen() || type == null || !allowEagerInit) {
404-
return doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit);
415+
return doGetBeanNamesForType(ResolvableType.forClass(type != null ? type : Object.class),
416+
includeNonSingletons, allowEagerInit);
405417
}
406418
Map<Class<?>, String[]> cache =
407419
(includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType);
408420
String[] resolvedBeanNames = cache.get(type);
409421
if (resolvedBeanNames != null) {
410422
return resolvedBeanNames;
411423
}
412-
resolvedBeanNames = doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit);
424+
resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forClass(type), includeNonSingletons, allowEagerInit);
413425
if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) {
414426
cache.put(type, resolvedBeanNames);
415427
}
416428
return resolvedBeanNames;
417429
}
418430

419-
private String[] doGetBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
431+
private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
420432
List<String> result = new ArrayList<String>();
421433

422434
// Check all bean definitions.

0 commit comments

Comments
 (0)