Skip to content

Commit 097bcfb

Browse files
committed
DefaultListableBeanFactory switches to thread-safe copying for names collections if necessary
Issue: SPR-13493 Issue: SPR-13123 Issue: SPR-12503
1 parent 627393a commit 097bcfb

File tree

3 files changed

+104
-21
lines changed

3 files changed

+104
-21
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,16 @@ protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) {
15351535
}
15361536
}
15371537

1538+
/**
1539+
* Check whether this factory's bean creation phase already started,
1540+
* i.e. whether any bean has been marked as created in the meantime.
1541+
* @since 4.2.2
1542+
* @see #markBeanAsCreated
1543+
*/
1544+
protected boolean hasBeanCreationStarted() {
1545+
return !this.alreadyCreated.isEmpty();
1546+
}
1547+
15381548
/**
15391549
* Get the object for the given bean instance, either the bean
15401550
* instance itself or its created object in case of a FactoryBean.

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

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
155155
private AutowireCandidateResolver autowireCandidateResolver = new SimpleAutowireCandidateResolver();
156156

157157
/** Map from dependency type to corresponding autowired value */
158-
private final Map<Class<?>, Object> resolvableDependencies = new HashMap<Class<?>, Object>(16);
158+
private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<Class<?>, Object>(16);
159159

160160
/** Map of bean definition objects, keyed by bean name */
161161
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);
@@ -167,16 +167,16 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
167167
private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<Class<?>, String[]>(64);
168168

169169
/** List of bean definition names, in registration order */
170-
private final List<String> beanDefinitionNames = new ArrayList<String>(64);
170+
private volatile List<String> beanDefinitionNames = new ArrayList<String>(64);
171171

172172
/** List of names of manually registered singletons, in registration order */
173-
private final Set<String> manualSingletonNames = new LinkedHashSet<String>(16);
174-
175-
/** Whether bean definition metadata may be cached for all beans */
176-
private boolean configurationFrozen = false;
173+
private volatile Set<String> manualSingletonNames = new LinkedHashSet<String>(16);
177174

178175
/** Cached array of bean definition names in case of frozen configuration */
179-
private String[] frozenBeanDefinitionNames;
176+
private volatile String[] frozenBeanDefinitionNames;
177+
178+
/** Whether bean definition metadata may be cached for all beans */
179+
private volatile boolean configurationFrozen = false;
180180

181181

182182
/**
@@ -848,13 +848,32 @@ else if (!beanDefinition.equals(oldBeanDefinition)) {
848848
"] with [" + beanDefinition + "]");
849849
}
850850
}
851+
this.beanDefinitionMap.put(beanName, beanDefinition);
851852
}
852853
else {
853-
this.beanDefinitionNames.add(beanName);
854-
this.manualSingletonNames.remove(beanName);
854+
if (hasBeanCreationStarted()) {
855+
// Cannot modify startup-time collection elements anymore (for stable iteration)
856+
synchronized (this.beanDefinitionMap) {
857+
this.beanDefinitionMap.put(beanName, beanDefinition);
858+
List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
859+
updatedDefinitions.addAll(this.beanDefinitionNames);
860+
updatedDefinitions.add(beanName);
861+
this.beanDefinitionNames = updatedDefinitions;
862+
if (this.manualSingletonNames.contains(beanName)) {
863+
Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
864+
updatedSingletons.remove(beanName);
865+
this.manualSingletonNames = updatedSingletons;
866+
}
867+
}
868+
}
869+
else {
870+
// Still in startup registration phase
871+
this.beanDefinitionMap.put(beanName, beanDefinition);
872+
this.beanDefinitionNames.add(beanName);
873+
this.manualSingletonNames.remove(beanName);
874+
}
855875
this.frozenBeanDefinitionNames = null;
856876
}
857-
this.beanDefinitionMap.put(beanName, beanDefinition);
858877

859878
if (oldBeanDefinition != null || containsSingleton(beanName)) {
860879
resetBeanDefinition(beanName);
@@ -872,7 +891,19 @@ public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionExc
872891
}
873892
throw new NoSuchBeanDefinitionException(beanName);
874893
}
875-
this.beanDefinitionNames.remove(beanName);
894+
895+
if (hasBeanCreationStarted()) {
896+
// Cannot modify startup-time collection elements anymore (for stable iteration)
897+
synchronized (this.beanDefinitionMap) {
898+
List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames);
899+
updatedDefinitions.remove(beanName);
900+
this.beanDefinitionNames = updatedDefinitions;
901+
}
902+
}
903+
else {
904+
// Still in startup registration phase
905+
this.beanDefinitionNames.remove(beanName);
906+
}
876907
this.frozenBeanDefinitionNames = null;
877908

878909
resetBeanDefinition(beanName);
@@ -914,9 +945,25 @@ protected boolean allowAliasOverriding() {
914945
@Override
915946
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
916947
super.registerSingleton(beanName, singletonObject);
917-
if (!this.beanDefinitionMap.containsKey(beanName)) {
918-
this.manualSingletonNames.add(beanName);
948+
949+
if (hasBeanCreationStarted()) {
950+
// Cannot modify startup-time collection elements anymore (for stable iteration)
951+
synchronized (this.beanDefinitionMap) {
952+
if (!this.beanDefinitionMap.containsKey(beanName)) {
953+
Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames.size() + 1);
954+
updatedSingletons.addAll(this.manualSingletonNames);
955+
updatedSingletons.add(beanName);
956+
this.manualSingletonNames = updatedSingletons;
957+
}
958+
}
919959
}
960+
else {
961+
// Still in startup registration phase
962+
if (!this.beanDefinitionMap.containsKey(beanName)) {
963+
this.manualSingletonNames.add(beanName);
964+
}
965+
}
966+
920967
clearByTypeCache();
921968
}
922969

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

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,7 +1224,7 @@ public void testExpressionInStringArray() {
12241224

12251225
RootBeanDefinition rbd = new RootBeanDefinition(PropertiesFactoryBean.class);
12261226
MutablePropertyValues pvs = new MutablePropertyValues();
1227-
pvs.add("locations", new String[] {"#{foo}"});
1227+
pvs.add("locations", new String[]{"#{foo}"});
12281228
rbd.setPropertyValues(pvs);
12291229
bf.registerBeanDefinition("myProperties", rbd);
12301230
Properties properties = (Properties) bf.getBean("myProperties");
@@ -2462,6 +2462,7 @@ public void testBeanPostProcessorWithWrappedObjectAndDisposableBean() {
24622462
public Object postProcessBeforeInitialization(Object bean, String beanName) {
24632463
return new TestBean();
24642464
}
2465+
24652466
@Override
24662467
public Object postProcessAfterInitialization(Object bean, String beanName) {
24672468
return bean;
@@ -2750,10 +2751,6 @@ public void resolveEmbeddedValue() throws Exception {
27502751
verify(r3, never()).resolveStringValue(isNull(String.class));
27512752
}
27522753

2753-
2754-
static class A { }
2755-
static class B { }
2756-
27572754
/**
27582755
* Test that by-type bean lookup caching is working effectively by searching for a
27592756
* bean of type B 10K times within a container having 1K additional beans of type A.
@@ -2764,23 +2761,52 @@ static class B { }
27642761
* under the 1000 ms timeout, usually ~= 300ms. With caching removed and on the same
27652762
* hardware the method will take ~13000 ms. See SPR-6870.
27662763
*/
2767-
@Test(timeout=1000)
2764+
@Test(timeout = 1000)
27682765
public void testByTypeLookupIsFastEnough() {
27692766
Assume.group(TestGroup.PERFORMANCE);
27702767
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
27712768

27722769
for (int i = 0; i < 1000; i++) {
2773-
bf.registerBeanDefinition("a"+i, new RootBeanDefinition(A.class));
2770+
bf.registerBeanDefinition("a" + i, new RootBeanDefinition(A.class));
27742771
}
27752772
bf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
27762773

27772774
bf.freezeConfiguration();
27782775

2779-
for (int i=0; i<10000; i++) {
2776+
for (int i = 0; i < 10000; i++) {
27802777
bf.getBean(B.class);
27812778
}
27822779
}
27832780

2781+
@Test(timeout = 1000)
2782+
public void testRegistrationOfManyBeanDefinitionsIsFastEnough() {
2783+
// Assume.group(TestGroup.PERFORMANCE);
2784+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
2785+
bf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
2786+
// bf.getBean("b");
2787+
2788+
for (int i = 0; i < 100000; i++) {
2789+
bf.registerBeanDefinition("a" + i, new RootBeanDefinition(A.class));
2790+
}
2791+
}
2792+
2793+
@Test(timeout = 1000)
2794+
public void testRegistrationOfManySingletonsIsFastEnough() {
2795+
// Assume.group(TestGroup.PERFORMANCE);
2796+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
2797+
bf.registerBeanDefinition("b", new RootBeanDefinition(B.class));
2798+
// bf.getBean("b");
2799+
2800+
for (int i = 0; i < 100000; i++) {
2801+
bf.registerSingleton("a" + i, new A());
2802+
}
2803+
}
2804+
2805+
2806+
static class A { }
2807+
2808+
static class B { }
2809+
27842810

27852811
public static class NoDependencies {
27862812

0 commit comments

Comments
 (0)