Skip to content

Commit c9e85ec

Browse files
committedMar 7, 2024
Introduce callback for singleton availability
Closes gh-21362
1 parent 1a8d64f commit c9e85ec

File tree

3 files changed

+44
-9
lines changed

3 files changed

+44
-9
lines changed
 

Diff for: ‎spring-beans/src/main/java/org/springframework/beans/factory/config/SingletonBeanRegistry.java

+13
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.beans.factory.config;
1818

19+
import java.util.function.Consumer;
20+
1921
import org.springframework.lang.Nullable;
2022

2123
/**
@@ -57,6 +59,17 @@ public interface SingletonBeanRegistry {
5759
*/
5860
void registerSingleton(String beanName, Object singletonObject);
5961

62+
/**
63+
* Add a callback to be triggered when the specified singleton becomes available
64+
* in the bean registry.
65+
* @param beanName the name of the bean
66+
* @param singletonConsumer a callback for reacting to the availability of the freshly
67+
* registered/created singleton instance (intended for follow-up steps before the bean is
68+
* actively used by other callers, not for modifying the given singleton instance itself)
69+
* @since 6.2
70+
*/
71+
void addSingletonCallback(String beanName, Consumer<Object> singletonConsumer);
72+
6073
/**
6174
* Return the (raw) singleton object registered under the given name.
6275
* <p>Only checks already instantiated singletons; does not return an Object

Diff for: ‎spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java

+22-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.beans.factory.support;
1818

1919
import java.util.Collections;
20-
import java.util.HashMap;
2120
import java.util.HashSet;
2221
import java.util.Iterator;
2322
import java.util.LinkedHashMap;
@@ -27,6 +26,7 @@
2726
import java.util.concurrent.ConcurrentHashMap;
2827
import java.util.concurrent.locks.Lock;
2928
import java.util.concurrent.locks.ReentrantLock;
29+
import java.util.function.Consumer;
3030

3131
import org.springframework.beans.factory.BeanCreationException;
3232
import org.springframework.beans.factory.BeanCreationNotAllowedException;
@@ -79,8 +79,11 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
7979
/** Cache of singleton objects: bean name to bean instance. */
8080
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
8181

82-
/** Cache of singleton factories: bean name to ObjectFactory. */
83-
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
82+
/** Creation-time registry of singleton factories: bean name to ObjectFactory. */
83+
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
84+
85+
/** Custom callbacks for singleton creation/registration. */
86+
private final Map<String, Consumer<Object>> singletonCallbacks = new ConcurrentHashMap<>(16);
8487

8588
/** Cache of early singleton objects: bean name to bean instance. */
8689
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
@@ -133,8 +136,8 @@ public void registerSingleton(String beanName, Object singletonObject) throws Il
133136
}
134137

135138
/**
136-
* Add the given singleton object to the singleton cache of this factory.
137-
* <p>To be called for eager registration of singletons.
139+
* Add the given singleton object to the singleton registry.
140+
* <p>To be called for exposure of freshly registered/created singletons.
138141
* @param beanName the name of the bean
139142
* @param singletonObject the singleton object
140143
*/
@@ -147,12 +150,17 @@ protected void addSingleton(String beanName, Object singletonObject) {
147150
this.singletonFactories.remove(beanName);
148151
this.earlySingletonObjects.remove(beanName);
149152
this.registeredSingletons.add(beanName);
153+
154+
Consumer<Object> callback = this.singletonCallbacks.get(beanName);
155+
if (callback != null) {
156+
callback.accept(singletonObject);
157+
}
150158
}
151159

152160
/**
153161
* Add the given singleton factory for building the specified singleton
154162
* if necessary.
155-
* <p>To be called for eager registration of singletons, e.g. to be able to
163+
* <p>To be called for early exposure purposes, e.g. to be able to
156164
* resolve circular references.
157165
* @param beanName the name of the bean
158166
* @param singletonFactory the factory for the singleton object
@@ -164,6 +172,11 @@ protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFa
164172
this.registeredSingletons.add(beanName);
165173
}
166174

175+
@Override
176+
public void addSingletonCallback(String beanName, Consumer<Object> singletonConsumer) {
177+
this.singletonCallbacks.put(beanName, singletonConsumer);
178+
}
179+
167180
@Override
168181
@Nullable
169182
public Object getSingleton(String beanName) {
@@ -262,6 +275,7 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
262275
}
263276
}
264277
}
278+
265279
if (this.singletonsCurrentlyInDestruction) {
266280
throw new BeanCreationNotAllowedException(beanName,
267281
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
@@ -343,8 +357,8 @@ protected void onSuppressedException(Exception ex) {
343357
}
344358

345359
/**
346-
* Remove the bean with the given name from the singleton cache of this factory,
347-
* to be able to clean up eager registration of a singleton if creation failed.
360+
* Remove the bean with the given name from the singleton registry, either on
361+
* regular destruction or on cleanup after early exposure when creation failed.
348362
* @param beanName the name of the bean
349363
*/
350364
protected void removeSingleton(String beanName) {

Diff for: ‎spring-beans/src/test/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistryTests.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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,6 +16,8 @@
1616

1717
package org.springframework.beans.factory.support;
1818

19+
import java.util.concurrent.atomic.AtomicBoolean;
20+
1921
import org.junit.jupiter.api.Test;
2022

2123
import org.springframework.beans.testfixture.beans.DerivedTestBean;
@@ -35,12 +37,18 @@ class DefaultSingletonBeanRegistryTests {
3537

3638
@Test
3739
void singletons() {
40+
AtomicBoolean tbFlag = new AtomicBoolean();
41+
beanRegistry.addSingletonCallback("tb", instance -> tbFlag.set(true));
3842
TestBean tb = new TestBean();
3943
beanRegistry.registerSingleton("tb", tb);
4044
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
45+
assertThat(tbFlag.get()).isTrue();
4146

47+
AtomicBoolean tb2Flag = new AtomicBoolean();
48+
beanRegistry.addSingletonCallback("tb2", instance -> tb2Flag.set(true));
4249
TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", TestBean::new);
4350
assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2);
51+
assertThat(tb2Flag.get()).isTrue();
4452

4553
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
4654
assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2);

0 commit comments

Comments
 (0)
Please sign in to comment.