Analyzing BootstrapContext and Its Default Implementation in Spring Boot
The BootstrapContext interface provides lazy access to shared or expensive singleton objects during startup, before the ApplicationContext becomes ready. It remains available throughout environment post-processing.
public interface BootstrapContext {
<T> T get(Class<T> requiredType) throws IllegalStateException;
<T> T getOrElse(Class<T> requiredType, T fallback);
<T> T getOrElseSupply(Class<T> requiredType, Supplier<T> fallbackSupplier);
<T, X extends Throwable> T getOrElseThrow(Class<T> requiredType, Supplier<? extends X> exSupplier) throws X;
<T> boolean isRegistered(Class<T> requiredType);
}
get: Retrieves an instance of the specified type if it has been registered; otherwise creates it. Throws if unavailable.getOrElse: Returns the registered instance or the supplied fallback value if unregistered.getOrElseSupply: Returns the registered instance or obtains one from the given supplier when missing.getOrElseThrow: Returns the registered instance or throws the exception produced by the given supplier.isRegistered: Checks whether a particular type has been registered.
ConfigurableBootstrapContext
public interface ConfigurableBootstrapContext extends BootstrapRegistry, BootstrapContext { }
This composite interface combines registration and retrieval capabilities, enabling full control over bootstrap configuration. Implementations can manage both instance suppliers and direct instance storage.
DefaultBootstrapContext Implementation
DefaultBootstrapContext is the out-of-the-box implementation used by Spring Boot.
public class DefaultBootstrapContext implements ConfigurableBootstrapContext {
private final Map<Class<?>, InstanceSupplier<?>> supplierMap = new HashMap<>();
private final Map<Class<?>, Object> instanceCache = new HashMap<>();
private final ApplicationEventMulticaster eventBroadcaster = new SimpleApplicationEventMulticaster();
public void close(ConfigurableApplicationContext appCtx) {
this.eventBroadcaster.multicastEvent(new BootstrapContextClosedEvent(this, appCtx));
}
}
supplierMap: Associates a type with itsInstanceSupplier, responsible for producing the instance.instanceCache: Holds already-created singleton instances keyed by type.eventBroadcaster: Dispatches bootstrap lifecycle events to listeners.
Registration Methods
public <T> void register(Class<T> type, InstanceSupplier<T> supplier) {
register(type, supplier, true);
}
public <T> void registerIfAbsent(Class<T> type, InstanceSupplier<T> supplier) {
register(type, supplier, false);
}
private <T> void register(Class<T> type, InstanceSupplier<T> supplier, boolean allowReplace) {
Assert.notNull(type, "Type must not be null");
Assert.notNull(supplier, "Supplier must not be null");
synchronized (this.supplierMap) {
boolean present = this.supplierMap.containsKey(type);
if (allowReplace || !present) {
Assert.state(!this.instanceCache.containsKey(type),
() -> type.getName() + " instance already created");
this.supplierMap.put(type, supplier);
}
}
}
registeralways overwrites an existing entry;registerIfAbsentonly registers when absent.- Thread safety is ensured via synchronization on
supplierMap.
public <T> boolean isRegistered(Class<T> type) {
synchronized (this.supplierMap) {
return this.supplierMap.containsKey(type);
}
}
@SuppressWarnings("unchecked")
public <T> InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type) {
synchronized (this.supplierMap) {
return (InstanceSupplier<T>) this.supplierMap.get(type);
}
}
public void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> lst) {
this.eventBroadcaster.addApplicationListener(lst);
}
Retrieval Methods
public <T> T get(Class<T> type) throws IllegalStateException {
return getOrElseThrow(type, () -> new IllegalStateException(type.getName() + " not registered"));
}
public <T> T getOrElse(Class<T> type, T alt) {
return getOrElseSupply(type, () -> alt);
}
public <T> T getOrElseSupply(Class<T> type, Supplier<T> altSupplier) {
synchronized (this.supplierMap) {
InstanceSupplier<?> supplier = this.supplierMap.get(type);
return supplier != null ? getInstance(type, supplier) : altSupplier.get();
}
}
public <T, X extends Throwable> T getOrElseThrow(Class<T> type, Supplier<? extends X> exSupplier) throws X {
synchronized (this.supplierMap) {
InstanceSupplier<?> supplier = this.supplierMap.get(type);
if (supplier == null) throw exSupplier.get();
return getInstance(type, supplier);
}
}
Both getOrElseSupply and getOrElseThrow delegate instance creation to getInstance when a supplier exists.
@SuppressWarnings("unchecked")
private <T> T getInstance(Class<T> type, InstanceSupplier<?> supplier) {
T obj = (T) this.instanceCache.get(type);
if (obj == null) {
obj = (T) supplier.get(this);
if (supplier.getScope() == Scope.SINGLETON) {
this.instanceCache.put(type, obj);
}
}
return obj;
}
- First checks
instanceCache; if missing, invokes the supplier. - Caches the result for singleton-scoped suppliers.
Lifecycle Close Method
When bootstrapping ends and the application context is prepared, close broadcasts BootstrapContextClosedEvent to all listeners added via addCloseListener. This allows cleanup or finalization tied to bootstrap completion.