Skip to content

Commit f75c01d

Browse files
committed
Merge branch cbeams/SPR-6870
* SPR-6870: Cache by-type lookups in DefaultListableBeanFactory Polish
2 parents f55a4a1 + 4c7a1c0 commit f75c01d

File tree

2 files changed

+92
-35
lines changed

2 files changed

+92
-35
lines changed

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

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@
2121
import java.io.ObjectInputStream;
2222
import java.io.ObjectStreamException;
2323
import java.io.Serializable;
24+
2425
import java.lang.annotation.Annotation;
2526
import java.lang.ref.Reference;
2627
import java.lang.ref.WeakReference;
27-
import java.lang.reflect.ParameterizedType;
28-
import java.lang.reflect.Type;
28+
2929
import java.security.AccessController;
3030
import java.security.PrivilegedAction;
31+
3132
import java.util.ArrayList;
3233
import java.util.Arrays;
3334
import java.util.Collection;
@@ -38,6 +39,7 @@
3839
import java.util.Map;
3940
import java.util.Set;
4041
import java.util.concurrent.ConcurrentHashMap;
42+
4143
import javax.inject.Provider;
4244

4345
import org.springframework.beans.BeansException;
@@ -90,6 +92,7 @@
9092
* @author Juergen Hoeller
9193
* @author Sam Brannen
9294
* @author Costin Leau
95+
* @author Chris Beams
9396
* @since 16 April 2001
9497
* @see StaticListableBeanFactory
9598
* @see PropertiesBeanDefinitionReader
@@ -98,7 +101,7 @@
98101
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
99102
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
100103

101-
private static Class javaxInjectProviderClass = null;
104+
private static Class<?> javaxInjectProviderClass = null;
102105

103106
static {
104107
ClassLoader cl = DefaultListableBeanFactory.class.getClassLoader();
@@ -128,11 +131,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
128131
private AutowireCandidateResolver autowireCandidateResolver = new SimpleAutowireCandidateResolver();
129132

130133
/** Map from dependency type to corresponding autowired value */
131-
private final Map<Class, Object> resolvableDependencies = new HashMap<Class, Object>();
134+
private final Map<Class<?>, Object> resolvableDependencies = new HashMap<Class<?>, Object>();
132135

133136
/** Map of bean definition objects, keyed by bean name */
134137
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
135138

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+
136145
/** List of bean definition names, in registration order */
137146
private final List<String> beanDefinitionNames = new ArrayList<String>();
138147

@@ -294,11 +303,26 @@ public String[] getBeanDefinitionNames() {
294303
}
295304
}
296305

297-
public String[] getBeanNamesForType(Class type) {
306+
public String[] getBeanNamesForType(Class<?> type) {
298307
return getBeanNamesForType(type, true, true);
299308
}
300309

301-
public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) {
310+
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) {
302326
List<String> result = new ArrayList<String>();
303327

304328
// Check all bean definitions.
@@ -441,7 +465,7 @@ public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> an
441465
*/
442466
public <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) {
443467
A ann = null;
444-
Class beanType = getType(beanName);
468+
Class<?> beanType = getType(beanName);
445469
if (beanType != null) {
446470
ann = AnnotationUtils.findAnnotation(beanType, annotationType);
447471
}
@@ -564,18 +588,18 @@ public void preInstantiateSingletons() throws BeansException {
564588
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
565589
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
566590
if (isFactoryBean(beanName)) {
567-
final FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName);
591+
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
568592
boolean isEagerInit;
569593
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
570594
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
571595
public Boolean run() {
572-
return ((SmartFactoryBean) factory).isEagerInit();
596+
return ((SmartFactoryBean<?>) factory).isEagerInit();
573597
}
574598
}, getAccessControlContext());
575599
}
576600
else {
577601
isEagerInit = (factory instanceof SmartFactoryBean &&
578-
((SmartFactoryBean) factory).isEagerInit());
602+
((SmartFactoryBean<?>) factory).isEagerInit());
579603
}
580604
if (isEagerInit) {
581605
getBean(beanName);
@@ -669,6 +693,10 @@ protected void resetBeanDefinition(String beanName) {
669693
destroySingleton(beanName);
670694
}
671695

696+
// Remove any assumptions about by-type mappings
697+
this.singletonBeanNamesByType.clear();
698+
this.nonSingletonBeanNamesByType.clear();
699+
672700
// Reset all bean definitions that have the given bean as parent (recursively).
673701
for (String bdName : this.beanDefinitionNames) {
674702
if (!beanName.equals(bdName)) {
@@ -723,7 +751,7 @@ protected Object doResolveDependency(DependencyDescriptor descriptor, Class<?> t
723751
}
724752

725753
if (type.isArray()) {
726-
Class componentType = type.getComponentType();
754+
Class<?> componentType = type.getComponentType();
727755
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, descriptor);
728756
if (matchingBeans.isEmpty()) {
729757
if (descriptor.isRequired()) {
@@ -738,7 +766,7 @@ protected Object doResolveDependency(DependencyDescriptor descriptor, Class<?> t
738766
return converter.convertIfNecessary(matchingBeans.values(), type);
739767
}
740768
else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
741-
Class elementType = descriptor.getCollectionType();
769+
Class<?> elementType = descriptor.getCollectionType();
742770
if (elementType == null) {
743771
if (descriptor.isRequired()) {
744772
throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]");
@@ -759,15 +787,15 @@ else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
759787
return converter.convertIfNecessary(matchingBeans.values(), type);
760788
}
761789
else if (Map.class.isAssignableFrom(type) && type.isInterface()) {
762-
Class keyType = descriptor.getMapKeyType();
790+
Class<?> keyType = descriptor.getMapKeyType();
763791
if (keyType == null || !String.class.isAssignableFrom(keyType)) {
764792
if (descriptor.isRequired()) {
765793
throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() +
766794
"] must be assignable to [java.lang.String]");
767795
}
768796
return null;
769797
}
770-
Class valueType = descriptor.getMapValueType();
798+
Class<?> valueType = descriptor.getMapValueType();
771799
if (valueType == null) {
772800
if (descriptor.isRequired()) {
773801
throw new FatalBeanException("No value type declared for map [" + type.getName() + "]");
@@ -828,12 +856,12 @@ else if (Map.class.isAssignableFrom(type) && type.isInterface()) {
828856
* @see #autowireConstructor
829857
*/
830858
protected Map<String, Object> findAutowireCandidates(
831-
String beanName, Class requiredType, DependencyDescriptor descriptor) {
859+
String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
832860

833861
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
834862
this, requiredType, true, descriptor.isEager());
835863
Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length);
836-
for (Class autowiringType : this.resolvableDependencies.keySet()) {
864+
for (Class<?> autowiringType : this.resolvableDependencies.keySet()) {
837865
if (autowiringType.isAssignableFrom(requiredType)) {
838866
Object autowiringValue = this.resolvableDependencies.get(autowiringType);
839867
autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
@@ -918,7 +946,7 @@ protected boolean matchesBeanName(String beanName, String candidateName) {
918946
* Raise a NoSuchBeanDefinitionException for an unresolvable dependency.
919947
*/
920948
private void raiseNoSuchBeanDefinitionException(
921-
Class type, String dependencyDescription, DependencyDescriptor descriptor)
949+
Class<?> type, String dependencyDescription, DependencyDescriptor descriptor)
922950
throws NoSuchBeanDefinitionException {
923951

924952
throw new NoSuchBeanDefinitionException(type, dependencyDescription,
@@ -967,6 +995,7 @@ protected Object writeReplace() throws ObjectStreamException {
967995
* Minimal id reference to the factory.
968996
* Resolved to the actual factory instance on deserialization.
969997
*/
998+
@SuppressWarnings("serial")
970999
private static class SerializedBeanFactoryReference implements Serializable {
9711000

9721001
private final String id;
@@ -976,7 +1005,7 @@ public SerializedBeanFactoryReference(String id) {
9761005
}
9771006

9781007
private Object readResolve() {
979-
Reference ref = serializableFactories.get(this.id);
1008+
Reference<?> ref = serializableFactories.get(this.id);
9801009
if (ref == null) {
9811010
throw new IllegalStateException(
9821011
"Cannot deserialize BeanFactory with id " + this.id + ": no factory registered for this id");
@@ -994,7 +1023,8 @@ private Object readResolve() {
9941023
/**
9951024
* Serializable ObjectFactory for lazy resolution of a dependency.
9961025
*/
997-
private class DependencyObjectFactory implements ObjectFactory, Serializable {
1026+
@SuppressWarnings("serial")
1027+
private class DependencyObjectFactory implements ObjectFactory<Object>, Serializable {
9981028

9991029
private final DependencyDescriptor descriptor;
10001030

@@ -1015,7 +1045,8 @@ public Object getObject() throws BeansException {
10151045
/**
10161046
* Serializable ObjectFactory for lazy resolution of a dependency.
10171047
*/
1018-
private class DependencyProvider extends DependencyObjectFactory implements Provider {
1048+
@SuppressWarnings("serial")
1049+
private class DependencyProvider extends DependencyObjectFactory implements Provider<Object> {
10191050

10201051
public DependencyProvider(DependencyDescriptor descriptor, String beanName) {
10211052
super(descriptor, beanName);

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

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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,26 +16,18 @@
1616

1717
package org.springframework.beans.factory;
1818

19-
import static org.hamcrest.CoreMatchers.is;
20-
import static org.junit.Assert.assertEquals;
21-
import static org.junit.Assert.assertFalse;
22-
import static org.junit.Assert.assertNotNull;
23-
import static org.junit.Assert.assertNotSame;
24-
import static org.junit.Assert.assertNull;
25-
import static org.junit.Assert.assertSame;
26-
import static org.junit.Assert.assertThat;
27-
import static org.junit.Assert.assertTrue;
28-
import static org.junit.Assert.fail;
29-
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
30-
3119
import java.lang.reflect.Field;
20+
3221
import java.net.MalformedURLException;
22+
3323
import java.security.AccessControlContext;
3424
import java.security.AccessController;
3525
import java.security.Principal;
3626
import java.security.PrivilegedAction;
27+
3728
import java.text.NumberFormat;
3829
import java.text.ParseException;
30+
3931
import java.util.Arrays;
4032
import java.util.Iterator;
4133
import java.util.List;
@@ -48,8 +40,10 @@
4840

4941
import org.apache.commons.logging.Log;
5042
import org.apache.commons.logging.LogFactory;
43+
5144
import org.junit.Ignore;
5245
import org.junit.Test;
46+
5347
import org.springframework.beans.BeansException;
5448
import org.springframework.beans.MutablePropertyValues;
5549
import org.springframework.beans.NotWritablePropertyException;
@@ -92,6 +86,10 @@
9286
import test.beans.NestedTestBean;
9387
import test.beans.TestBean;
9488

89+
import static org.hamcrest.CoreMatchers.*;
90+
91+
import static org.junit.Assert.*;
92+
9593
/**
9694
* Tests properties population and autowire behavior.
9795
*
@@ -2159,13 +2157,41 @@ public Object run() {
21592157
@Test
21602158
public void testContainsBeanReturnsTrueEvenForAbstractBeanDefinition() {
21612159
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
2162-
bf.registerBeanDefinition("abs",
2163-
rootBeanDefinition(TestBean.class).setAbstract(true).getBeanDefinition());
2160+
bf.registerBeanDefinition("abs", BeanDefinitionBuilder
2161+
.rootBeanDefinition(TestBean.class).setAbstract(true).getBeanDefinition());
21642162
assertThat(bf.containsBean("abs"), is(true));
21652163
assertThat(bf.containsBean("bogus"), is(false));
21662164
}
21672165

21682166

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+
21692195
public static class NoDependencies {
21702196

21712197
private NoDependencies() {

0 commit comments

Comments
 (0)