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 c4f53525306..afabaac09df 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 @@ -92,6 +92,7 @@ import org.springframework.util.StringUtils; * @author Juergen Hoeller * @author Sam Brannen * @author Costin Leau + * @author Chris Beams * @since 16 April 2001 * @see StaticListableBeanFactory * @see PropertiesBeanDefinitionReader @@ -135,6 +136,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto /** Map of bean definition objects, keyed by bean name */ private final Map beanDefinitionMap = new ConcurrentHashMap(); + /** Map of singleton bean names keyed by bean class */ + private final Map, String[]> singletonBeanNamesByType = new ConcurrentHashMap, String[]>(); + + /** Map of non-singleton bean names keyed by bean class */ + private final Map, String[]> nonSingletonBeanNamesByType = new ConcurrentHashMap, String[]>(); + /** List of bean definition names, in registration order */ private final List beanDefinitionNames = new ArrayList(); @@ -301,6 +308,21 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) { + if (type == null || !allowEagerInit) { + return this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit); + } + Map, String[]> cache = includeNonSingletons ? + this.nonSingletonBeanNamesByType : this.singletonBeanNamesByType; + String[] resolvedBeanNames = cache.get(type); + if (resolvedBeanNames != null) { + return resolvedBeanNames; + } + resolvedBeanNames = this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit); + cache.put(type, resolvedBeanNames); + return resolvedBeanNames; + } + + private String[] doGetBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) { List result = new ArrayList(); // Check all bean definitions. @@ -671,6 +693,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto destroySingleton(beanName); } + // Remove any assumptions about by-type mappings + this.singletonBeanNamesByType.clear(); + this.nonSingletonBeanNamesByType.clear(); + // Reset all bean definitions that have the given bean as parent (recursively). for (String bdName : this.beanDefinitionNames) { if (!beanName.equals(bdName)) { diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index 8505da7cba1..c123347482c 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -2164,6 +2164,34 @@ public class DefaultListableBeanFactoryTests { } + static class A { } + static class B { } + + /** + * Test that by-type bean lookup caching is working effectively by searching for a + * bean of type B 10K times within a container having 1K additional beans of type A. + * Prior to by-type caching, each bean lookup would traverse the entire container + * (all 1001 beans), performing expensive assignability checks, etc. Now these + * operations are necessary only once, providing a dramatic performance improvement. + * On load-free modern hardware (e.g. an 8-core MPB), this method should complete well + * under the 1000 ms timeout, usually ~= 300ms. With caching removed and on the same + * hardware the method will take ~13000 ms. See SPR-6870. + */ + @Test(timeout=1000) + public void testByTypeLookupIsFastEnough() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + for (int i=0; i<1000; i++) { + bf.registerBeanDefinition("a"+i, new RootBeanDefinition(A.class)); + } + bf.registerBeanDefinition("b", new RootBeanDefinition(B.class)); + + for (int i=0; i<10000; i++) { + bf.getBean(B.class); + } + } + + public static class NoDependencies { private NoDependencies() {