Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
  • Loading branch information...
commit 4c7a1c0a5403b35dd812dae1f2a753538928bb32 1 parent db1cb13
@cbeams cbeams authored
View
26 ...s/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
@@ -92,6 +92,7 @@
* @author Juergen Hoeller
* @author Sam Brannen
* @author Costin Leau
+ * @author Chris Beams
* @since 16 April 2001
* @see StaticListableBeanFactory
* @see PropertiesBeanDefinitionReader
@@ -135,6 +136,12 @@
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
+ /** Map of singleton bean names keyed by bean class */
+ private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>();
+
+ /** Map of non-singleton bean names keyed by bean class */
+ private final Map<Class<?>, String[]> nonSingletonBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>();
+
/** List of bean definition names, in registration order */
private final List<String> beanDefinitionNames = new ArrayList<String>();
@@ -301,6 +308,21 @@ public int getBeanDefinitionCount() {
}
public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
+ if (type == null || !allowEagerInit) {
+ return this.doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit);
+ }
+ Map<Class<?>, 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<String> result = new ArrayList<String>();
// Check all bean definitions.
@@ -671,6 +693,10 @@ protected void resetBeanDefinition(String beanName) {
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)) {
View
28 ...eans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
@@ -2164,6 +2164,34 @@ public void testContainsBeanReturnsTrueEvenForAbstractBeanDefinition() {
}
+ 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() {
Please sign in to comment.
Something went wrong with that request. Please try again.