Browse Source

Merge branch cbeams/SPR-6870

* SPR-6870:
  Cache by-type lookups in DefaultListableBeanFactory
  Polish
pull/86/head
Chris Beams 14 years ago
parent
commit
f75c01d191
  1. 71
      spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
  2. 56
      spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java

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

@ -21,13 +21,14 @@ import java.io.NotSerializableException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectStreamException; import java.io.ObjectStreamException;
import java.io.Serializable; import java.io.Serializable;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.ref.Reference; import java.lang.ref.Reference;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -38,6 +39,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Provider; import javax.inject.Provider;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@ -90,6 +92,7 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen * @author Sam Brannen
* @author Costin Leau * @author Costin Leau
* @author Chris Beams
* @since 16 April 2001 * @since 16 April 2001
* @see StaticListableBeanFactory * @see StaticListableBeanFactory
* @see PropertiesBeanDefinitionReader * @see PropertiesBeanDefinitionReader
@ -98,7 +101,7 @@ import org.springframework.util.StringUtils;
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
private static Class javaxInjectProviderClass = null; private static Class<?> javaxInjectProviderClass = null;
static { static {
ClassLoader cl = DefaultListableBeanFactory.class.getClassLoader(); ClassLoader cl = DefaultListableBeanFactory.class.getClassLoader();
@ -128,11 +131,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
private AutowireCandidateResolver autowireCandidateResolver = new SimpleAutowireCandidateResolver(); private AutowireCandidateResolver autowireCandidateResolver = new SimpleAutowireCandidateResolver();
/** Map from dependency type to corresponding autowired value */ /** Map from dependency type to corresponding autowired value */
private final Map<Class, Object> resolvableDependencies = new HashMap<Class, Object>(); private final Map<Class<?>, Object> resolvableDependencies = new HashMap<Class<?>, Object>();
/** Map of bean definition objects, keyed by bean name */ /** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(); 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 */ /** List of bean definition names, in registration order */
private final List<String> beanDefinitionNames = new ArrayList<String>(); private final List<String> beanDefinitionNames = new ArrayList<String>();
@ -294,11 +303,26 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
} }
} }
public String[] getBeanNamesForType(Class type) { public String[] getBeanNamesForType(Class<?> type) {
return getBeanNamesForType(type, true, true); return getBeanNamesForType(type, true, true);
} }
public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) { 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>(); List<String> result = new ArrayList<String>();
// Check all bean definitions. // Check all bean definitions.
@ -441,7 +465,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
*/ */
public <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) { public <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) {
A ann = null; A ann = null;
Class beanType = getType(beanName); Class<?> beanType = getType(beanName);
if (beanType != null) { if (beanType != null) {
ann = AnnotationUtils.findAnnotation(beanType, annotationType); ann = AnnotationUtils.findAnnotation(beanType, annotationType);
} }
@ -564,18 +588,18 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) { if (isFactoryBean(beanName)) {
final FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit; boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() { isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
public Boolean run() { public Boolean run() {
return ((SmartFactoryBean) factory).isEagerInit(); return ((SmartFactoryBean<?>) factory).isEagerInit();
} }
}, getAccessControlContext()); }, getAccessControlContext());
} }
else { else {
isEagerInit = (factory instanceof SmartFactoryBean && isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean) factory).isEagerInit()); ((SmartFactoryBean<?>) factory).isEagerInit());
} }
if (isEagerInit) { if (isEagerInit) {
getBean(beanName); getBean(beanName);
@ -669,6 +693,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
destroySingleton(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). // Reset all bean definitions that have the given bean as parent (recursively).
for (String bdName : this.beanDefinitionNames) { for (String bdName : this.beanDefinitionNames) {
if (!beanName.equals(bdName)) { if (!beanName.equals(bdName)) {
@ -723,7 +751,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
} }
if (type.isArray()) { if (type.isArray()) {
Class componentType = type.getComponentType(); Class<?> componentType = type.getComponentType();
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, descriptor); Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, descriptor);
if (matchingBeans.isEmpty()) { if (matchingBeans.isEmpty()) {
if (descriptor.isRequired()) { if (descriptor.isRequired()) {
@ -738,7 +766,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return converter.convertIfNecessary(matchingBeans.values(), type); return converter.convertIfNecessary(matchingBeans.values(), type);
} }
else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
Class elementType = descriptor.getCollectionType(); Class<?> elementType = descriptor.getCollectionType();
if (elementType == null) { if (elementType == null) {
if (descriptor.isRequired()) { if (descriptor.isRequired()) {
throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]"); throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]");
@ -759,7 +787,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return converter.convertIfNecessary(matchingBeans.values(), type); return converter.convertIfNecessary(matchingBeans.values(), type);
} }
else if (Map.class.isAssignableFrom(type) && type.isInterface()) { else if (Map.class.isAssignableFrom(type) && type.isInterface()) {
Class keyType = descriptor.getMapKeyType(); Class<?> keyType = descriptor.getMapKeyType();
if (keyType == null || !String.class.isAssignableFrom(keyType)) { if (keyType == null || !String.class.isAssignableFrom(keyType)) {
if (descriptor.isRequired()) { if (descriptor.isRequired()) {
throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() + throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() +
@ -767,7 +795,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
} }
return null; return null;
} }
Class valueType = descriptor.getMapValueType(); Class<?> valueType = descriptor.getMapValueType();
if (valueType == null) { if (valueType == null) {
if (descriptor.isRequired()) { if (descriptor.isRequired()) {
throw new FatalBeanException("No value type declared for map [" + type.getName() + "]"); throw new FatalBeanException("No value type declared for map [" + type.getName() + "]");
@ -828,12 +856,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
* @see #autowireConstructor * @see #autowireConstructor
*/ */
protected Map<String, Object> findAutowireCandidates( protected Map<String, Object> findAutowireCandidates(
String beanName, Class requiredType, DependencyDescriptor descriptor) { String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this, requiredType, true, descriptor.isEager()); this, requiredType, true, descriptor.isEager());
Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length); Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length);
for (Class autowiringType : this.resolvableDependencies.keySet()) { for (Class<?> autowiringType : this.resolvableDependencies.keySet()) {
if (autowiringType.isAssignableFrom(requiredType)) { if (autowiringType.isAssignableFrom(requiredType)) {
Object autowiringValue = this.resolvableDependencies.get(autowiringType); Object autowiringValue = this.resolvableDependencies.get(autowiringType);
autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType); autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
@ -918,7 +946,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
* Raise a NoSuchBeanDefinitionException for an unresolvable dependency. * Raise a NoSuchBeanDefinitionException for an unresolvable dependency.
*/ */
private void raiseNoSuchBeanDefinitionException( private void raiseNoSuchBeanDefinitionException(
Class type, String dependencyDescription, DependencyDescriptor descriptor) Class<?> type, String dependencyDescription, DependencyDescriptor descriptor)
throws NoSuchBeanDefinitionException { throws NoSuchBeanDefinitionException {
throw new NoSuchBeanDefinitionException(type, dependencyDescription, throw new NoSuchBeanDefinitionException(type, dependencyDescription,
@ -967,6 +995,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
* Minimal id reference to the factory. * Minimal id reference to the factory.
* Resolved to the actual factory instance on deserialization. * Resolved to the actual factory instance on deserialization.
*/ */
@SuppressWarnings("serial")
private static class SerializedBeanFactoryReference implements Serializable { private static class SerializedBeanFactoryReference implements Serializable {
private final String id; private final String id;
@ -976,7 +1005,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
} }
private Object readResolve() { private Object readResolve() {
Reference ref = serializableFactories.get(this.id); Reference<?> ref = serializableFactories.get(this.id);
if (ref == null) { if (ref == null) {
throw new IllegalStateException( throw new IllegalStateException(
"Cannot deserialize BeanFactory with id " + this.id + ": no factory registered for this id"); "Cannot deserialize BeanFactory with id " + this.id + ": no factory registered for this id");
@ -994,7 +1023,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
/** /**
* Serializable ObjectFactory for lazy resolution of a dependency. * Serializable ObjectFactory for lazy resolution of a dependency.
*/ */
private class DependencyObjectFactory implements ObjectFactory, Serializable { @SuppressWarnings("serial")
private class DependencyObjectFactory implements ObjectFactory<Object>, Serializable {
private final DependencyDescriptor descriptor; private final DependencyDescriptor descriptor;
@ -1015,7 +1045,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
/** /**
* Serializable ObjectFactory for lazy resolution of a dependency. * Serializable ObjectFactory for lazy resolution of a dependency.
*/ */
private class DependencyProvider extends DependencyObjectFactory implements Provider { @SuppressWarnings("serial")
private class DependencyProvider extends DependencyObjectFactory implements Provider<Object> {
public DependencyProvider(DependencyDescriptor descriptor, String beanName) { public DependencyProvider(DependencyDescriptor descriptor, String beanName) {
super(descriptor, beanName); super(descriptor, beanName);

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2011 the original author or authors. * Copyright 2002-2012 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,26 +16,18 @@
package org.springframework.beans.factory; package org.springframework.beans.factory;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.security.AccessControlContext; import java.security.AccessControlContext;
import java.security.AccessController; import java.security.AccessController;
import java.security.Principal; import java.security.Principal;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.ParseException; import java.text.ParseException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -48,8 +40,10 @@ import javax.security.auth.Subject;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.NotWritablePropertyException; import org.springframework.beans.NotWritablePropertyException;
@ -92,6 +86,10 @@ import test.beans.LifecycleBean;
import test.beans.NestedTestBean; import test.beans.NestedTestBean;
import test.beans.TestBean; import test.beans.TestBean;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
/** /**
* Tests properties population and autowire behavior. * Tests properties population and autowire behavior.
* *
@ -2159,13 +2157,41 @@ public class DefaultListableBeanFactoryTests {
@Test @Test
public void testContainsBeanReturnsTrueEvenForAbstractBeanDefinition() { public void testContainsBeanReturnsTrueEvenForAbstractBeanDefinition() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerBeanDefinition("abs", bf.registerBeanDefinition("abs", BeanDefinitionBuilder
rootBeanDefinition(TestBean.class).setAbstract(true).getBeanDefinition()); .rootBeanDefinition(TestBean.class).setAbstract(true).getBeanDefinition());
assertThat(bf.containsBean("abs"), is(true)); assertThat(bf.containsBean("abs"), is(true));
assertThat(bf.containsBean("bogus"), is(false)); assertThat(bf.containsBean("bogus"), is(false));
} }
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 { public static class NoDependencies {
private NoDependencies() { private NoDependencies() {

Loading…
Cancel
Save