Introduce callback for singleton availability

Closes gh-21362
This commit is contained in:
Juergen Hoeller 2024-03-07 09:57:29 +01:00
parent 1a8d64f9d8
commit c9e85ec297
3 changed files with 44 additions and 9 deletions

View File

@ -16,6 +16,8 @@
package org.springframework.beans.factory.config; package org.springframework.beans.factory.config;
import java.util.function.Consumer;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/** /**
@ -57,6 +59,17 @@ public interface SingletonBeanRegistry {
*/ */
void registerSingleton(String beanName, Object singletonObject); void registerSingleton(String beanName, Object singletonObject);
/**
* Add a callback to be triggered when the specified singleton becomes available
* in the bean registry.
* @param beanName the name of the bean
* @param singletonConsumer a callback for reacting to the availability of the freshly
* registered/created singleton instance (intended for follow-up steps before the bean is
* actively used by other callers, not for modifying the given singleton instance itself)
* @since 6.2
*/
void addSingletonCallback(String beanName, Consumer<Object> singletonConsumer);
/** /**
* Return the (raw) singleton object registered under the given name. * Return the (raw) singleton object registered under the given name.
* <p>Only checks already instantiated singletons; does not return an Object * <p>Only checks already instantiated singletons; does not return an Object

View File

@ -17,7 +17,6 @@
package org.springframework.beans.factory.support; package org.springframework.beans.factory.support;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -27,6 +26,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCreationNotAllowedException; import org.springframework.beans.factory.BeanCreationNotAllowedException;
@ -79,8 +79,11 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
/** Cache of singleton objects: bean name to bean instance. */ /** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */ /** Creation-time registry of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
/** Custom callbacks for singleton creation/registration. */
private final Map<String, Consumer<Object>> singletonCallbacks = new ConcurrentHashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */ /** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
@ -133,8 +136,8 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
} }
/** /**
* Add the given singleton object to the singleton cache of this factory. * Add the given singleton object to the singleton registry.
* <p>To be called for eager registration of singletons. * <p>To be called for exposure of freshly registered/created singletons.
* @param beanName the name of the bean * @param beanName the name of the bean
* @param singletonObject the singleton object * @param singletonObject the singleton object
*/ */
@ -147,12 +150,17 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
this.singletonFactories.remove(beanName); this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName); this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName); this.registeredSingletons.add(beanName);
Consumer<Object> callback = this.singletonCallbacks.get(beanName);
if (callback != null) {
callback.accept(singletonObject);
}
} }
/** /**
* Add the given singleton factory for building the specified singleton * Add the given singleton factory for building the specified singleton
* if necessary. * if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to * <p>To be called for early exposure purposes, e.g. to be able to
* resolve circular references. * resolve circular references.
* @param beanName the name of the bean * @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object * @param singletonFactory the factory for the singleton object
@ -164,6 +172,11 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
this.registeredSingletons.add(beanName); this.registeredSingletons.add(beanName);
} }
@Override
public void addSingletonCallback(String beanName, Consumer<Object> singletonConsumer) {
this.singletonCallbacks.put(beanName, singletonConsumer);
}
@Override @Override
@Nullable @Nullable
public Object getSingleton(String beanName) { public Object getSingleton(String beanName) {
@ -262,6 +275,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
} }
} }
} }
if (this.singletonsCurrentlyInDestruction) { if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName, throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " + "Singleton bean creation not allowed while singletons of this factory are in destruction " +
@ -343,8 +357,8 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
} }
/** /**
* Remove the bean with the given name from the singleton cache of this factory, * Remove the bean with the given name from the singleton registry, either on
* to be able to clean up eager registration of a singleton if creation failed. * regular destruction or on cleanup after early exposure when creation failed.
* @param beanName the name of the bean * @param beanName the name of the bean
*/ */
protected void removeSingleton(String beanName) { protected void removeSingleton(String beanName) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 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,6 +16,8 @@
package org.springframework.beans.factory.support; package org.springframework.beans.factory.support;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.testfixture.beans.DerivedTestBean; import org.springframework.beans.testfixture.beans.DerivedTestBean;
@ -35,12 +37,18 @@ class DefaultSingletonBeanRegistryTests {
@Test @Test
void singletons() { void singletons() {
AtomicBoolean tbFlag = new AtomicBoolean();
beanRegistry.addSingletonCallback("tb", instance -> tbFlag.set(true));
TestBean tb = new TestBean(); TestBean tb = new TestBean();
beanRegistry.registerSingleton("tb", tb); beanRegistry.registerSingleton("tb", tb);
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb); assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
assertThat(tbFlag.get()).isTrue();
AtomicBoolean tb2Flag = new AtomicBoolean();
beanRegistry.addSingletonCallback("tb2", instance -> tb2Flag.set(true));
TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", TestBean::new); TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", TestBean::new);
assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2); assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2);
assertThat(tb2Flag.get()).isTrue();
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb); assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2); assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2);