Skip to content
This repository was archived by the owner on Oct 11, 2024. It is now read-only.

Commit 4c7a1c0

Browse files
committed
Cache by-type lookups in DefaultListableBeanFactory
Prior to this change, by-type lookups using DLBF#getBeanNamesForType required traversal of all bean definitions within the bean factory in order to inspect their bean class for assignability to the target type. These operations are comparatively expensive and when there are a large number of beans registered within the container coupled with a large number of by-type lookups at runtime, the performance impact can be severe. The test introduced here demonstrates such a scenario clearly. This performance problem is likely to manifest in large Spring-based applications using non-singleton beans, particularly request-scoped beans that may be created and wired many thousands of times per second. This commit introduces a simple ConcurrentHashMap-based caching strategy for by-type lookups; container-wide assignability checks happen only once on the first by-type lookup and are afterwards cached by type with the values in the map being an array of all bean names assignable to that type. This means that at runtime when creating and autowiring non-singleton beans, the cost of by-type lookups is reduced to that of ConcurrentHashMap#get. Issue: SPR-6870
1 parent db1cb13 commit 4c7a1c0

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

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

+26
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
* @author Juergen Hoeller
9393
* @author Sam Brannen
9494
* @author Costin Leau
95+
* @author Chris Beams
9596
* @since 16 April 2001
9697
* @see StaticListableBeanFactory
9798
* @see PropertiesBeanDefinitionReader
@@ -135,6 +136,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
135136
/** Map of bean definition objects, keyed by bean name */
136137
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
137138

139+
/** Map of singleton bean names keyed by bean class */
140+
private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>();
141+
142+
/** Map of non-singleton bean names keyed by bean class */
143+
private final Map<Class<?>, String[]> nonSingletonBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>();
144+
138145
/** List of bean definition names, in registration order */
139146
private final List<String> beanDefinitionNames = new ArrayList<String>();
140147

@@ -301,6 +308,21 @@ public String[] getBeanNamesForType(Class<?> type) {
301308
}
302309

303310
public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
311+
if (type == null || !allowEagerInit) {
312+
return this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit);
313+
}
314+
Map<Class<?>, String[]> cache = includeNonSingletons ?
315+
this.nonSingletonBeanNamesByType : this.singletonBeanNamesByType;
316+
String[] resolvedBeanNames = cache.get(type);
317+
if (resolvedBeanNames != null) {
318+
return resolvedBeanNames;
319+
}
320+
resolvedBeanNames = this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit);
321+
cache.put(type, resolvedBeanNames);
322+
return resolvedBeanNames;
323+
}
324+
325+
private String[] doGetBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
304326
List<String> result = new ArrayList<String>();
305327

306328
// Check all bean definitions.
@@ -671,6 +693,10 @@ protected void resetBeanDefinition(String beanName) {
671693
destroySingleton(beanName);
672694
}
673695

696+
// Remove any assumptions about by-type mappings
697+
this.singletonBeanNamesByType.clear();
698+
this.nonSingletonBeanNamesByType.clear();
699+
674700
// Reset all bean definitions that have the given bean as parent (recursively).
675701
for (String bdName : this.beanDefinitionNames) {
676702
if (!beanName.equals(bdName)) {

spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java

+28
Original file line numberDiff line numberDiff line change
@@ -2164,6 +2164,34 @@ public void testContainsBeanReturnsTrueEvenForAbstractBeanDefinition() {
21642164
}
21652165

21662166

2167+
static class A { }
2168+
static class B { }
2169+
2170+
/**
2171+
* Test that by-type bean lookup caching is working effectively by searching for a
2172+
* bean of type B 10K times within a container having 1K additional beans of type A.
2173+
* Prior to by-type caching, each bean lookup would traverse the entire container
2174+
* (all 1001 beans), performing expensive assignability checks, etc. Now these
2175+
* operations are necessary only once, providing a dramatic performance improvement.
2176+
* On load-free modern hardware (e.g. an 8-core MPB), this method should complete well
2177+
* under the 1000 ms timeout, usually ~= 300ms. With caching removed and on the same
2178+
* hardware the method will take ~13000 ms. See SPR-6870.
2179+
*/
2180+
@Test(timeout=1000)
2181+
public void testByTypeLookupIsFastEnough() {
2182+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
2183+
2184+
for (int i=0; i<1000; i++) {
2185+
bf.registerBeanDefinition("a"+i, new RootBeanDefinition(A.class));
2186+
}
2187+
bf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
2188+
2189+
for (int i=0; i<10000; i++) {
2190+
bf.getBean(B.class);
2191+
}
2192+
}
2193+
2194+
21672195
public static class NoDependencies {
21682196

21692197
private NoDependencies() {

0 commit comments

Comments
 (0)