Introduce background bootstrapping for individual singleton beans
Closes gh-13410 Closes gh-19487 See gh-23501
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,8 +32,8 @@ public class BeanCurrentlyInCreationException extends BeanCreationException {
|
||||
* @param beanName the name of the bean requested
|
||||
*/
|
||||
public BeanCurrentlyInCreationException(String beanName) {
|
||||
super(beanName,
|
||||
"Requested bean is currently in creation: Is there an unresolvable circular reference?");
|
||||
super(beanName, "Requested bean is currently in creation: "+
|
||||
"Is there an unresolvable circular reference or an asynchronous initialization dependency?");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.beans.factory.config;
|
||||
|
||||
import java.beans.PropertyEditor;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.beans.PropertyEditorRegistrar;
|
||||
import org.springframework.beans.PropertyEditorRegistry;
|
||||
@@ -25,6 +26,7 @@ import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.HierarchicalBeanFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.metrics.ApplicationStartup;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -146,6 +148,22 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single
|
||||
@Nullable
|
||||
BeanExpressionResolver getBeanExpressionResolver();
|
||||
|
||||
/**
|
||||
* Set the {@link Executor} (possibly a {@link org.springframework.core.task.TaskExecutor})
|
||||
* for background bootstrapping.
|
||||
* @since 6.2
|
||||
* @see AbstractBeanDefinition#setBackgroundInit
|
||||
*/
|
||||
void setBootstrapExecutor(@Nullable Executor executor);
|
||||
|
||||
/**
|
||||
* Return the {@link Executor} (possibly a {@link org.springframework.core.task.TaskExecutor})
|
||||
* for background bootstrapping, if any.
|
||||
* @since 6.2
|
||||
*/
|
||||
@Nullable
|
||||
Executor getBootstrapExecutor();
|
||||
|
||||
/**
|
||||
* Specify a {@link ConversionService} to use for converting
|
||||
* property values, as an alternative to JavaBeans PropertyEditors.
|
||||
|
||||
@@ -173,6 +173,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
||||
|
||||
private boolean abstractFlag = false;
|
||||
|
||||
private boolean backgroundInit = false;
|
||||
|
||||
@Nullable
|
||||
private Boolean lazyInit;
|
||||
|
||||
@@ -280,6 +282,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
||||
if (originalAbd.hasMethodOverrides()) {
|
||||
setMethodOverrides(new MethodOverrides(originalAbd.getMethodOverrides()));
|
||||
}
|
||||
setBackgroundInit(originalAbd.isBackgroundInit());
|
||||
Boolean lazyInit = originalAbd.getLazyInit();
|
||||
if (lazyInit != null) {
|
||||
setLazyInit(lazyInit);
|
||||
@@ -358,6 +361,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
||||
if (otherAbd.hasMethodOverrides()) {
|
||||
getMethodOverrides().addOverrides(otherAbd.getMethodOverrides());
|
||||
}
|
||||
setBackgroundInit(otherAbd.isBackgroundInit());
|
||||
Boolean lazyInit = otherAbd.getLazyInit();
|
||||
if (lazyInit != null) {
|
||||
setLazyInit(lazyInit);
|
||||
@@ -572,6 +576,37 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
||||
return this.abstractFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the bootstrap mode for this bean: default is {@code false} for using
|
||||
* the main pre-instantiation thread for non-lazy singleton beans and the caller
|
||||
* thread for prototype beans.
|
||||
* <p>Set this flag to {@code true} to allow for instantiating this bean on a
|
||||
* background thread. For a non-lazy singleton, a background pre-instantiation
|
||||
* thread can be used then, while still enforcing the completion at the end of
|
||||
* {@link DefaultListableBeanFactory#preInstantiateSingletons()}.
|
||||
* For a lazy singleton, a background pre-instantiation thread can be used as well
|
||||
* - with completion allowed at a later point, enforcing it when actually accessed.
|
||||
* <p>Note that this flag may be ignored by bean factories not set up for
|
||||
* background bootstrapping, always applying single-threaded bootstrapping
|
||||
* for non-lazy singleton beans.
|
||||
* @since 6.2
|
||||
* @see #setLazyInit
|
||||
* @see DefaultListableBeanFactory#setBootstrapExecutor
|
||||
*/
|
||||
public void setBackgroundInit(boolean backgroundInit) {
|
||||
this.backgroundInit = backgroundInit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bootstrap mode for this bean: default is {@code false} for using
|
||||
* the main pre-instantiation thread for non-lazy singleton beans and the caller
|
||||
* thread for prototype beans.
|
||||
* @since 6.2
|
||||
*/
|
||||
public boolean isBackgroundInit() {
|
||||
return this.backgroundInit;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>The default is {@code false}.
|
||||
|
||||
@@ -1445,11 +1445,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
* @param mbd the merged bean definition to check
|
||||
* @param beanName the name of the bean
|
||||
* @param args the arguments for bean creation, if any
|
||||
* @throws BeanDefinitionStoreException in case of validation failure
|
||||
*/
|
||||
protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args)
|
||||
throws BeanDefinitionStoreException {
|
||||
|
||||
protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args) {
|
||||
if (mbd.isAbstract()) {
|
||||
throw new BeanIsAbstractException(beanName);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
@@ -69,6 +72,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.config.NamedBeanHolder;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
@@ -83,6 +87,7 @@ import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.CompositeIterator;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -151,6 +156,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
/** Whether to allow eager class loading even for lazy-init beans. */
|
||||
private boolean allowEagerClassLoading = true;
|
||||
|
||||
@Nullable
|
||||
private Executor bootstrapExecutor;
|
||||
|
||||
/** Optional OrderComparator for dependency Lists and arrays. */
|
||||
@Nullable
|
||||
private Comparator<Object> dependencyComparator;
|
||||
@@ -189,6 +197,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
/** Whether bean definition metadata may be cached for all beans. */
|
||||
private volatile boolean configurationFrozen;
|
||||
|
||||
private final NamedThreadLocal<PreInstantiation> preInstantiationThread =
|
||||
new NamedThreadLocal<>("Pre-instantiation thread marker");
|
||||
|
||||
|
||||
/**
|
||||
* Create a new DefaultListableBeanFactory.
|
||||
@@ -273,6 +284,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return this.allowEagerClassLoading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBootstrapExecutor(@Nullable Executor bootstrapExecutor) {
|
||||
this.bootstrapExecutor = bootstrapExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Executor getBootstrapExecutor() {
|
||||
return this.bootstrapExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link java.util.Comparator} for dependency Lists and arrays.
|
||||
* @since 4.0
|
||||
@@ -319,6 +341,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
if (otherFactory instanceof DefaultListableBeanFactory otherListableFactory) {
|
||||
this.allowBeanDefinitionOverriding = otherListableFactory.allowBeanDefinitionOverriding;
|
||||
this.allowEagerClassLoading = otherListableFactory.allowEagerClassLoading;
|
||||
this.bootstrapExecutor = otherListableFactory.bootstrapExecutor;
|
||||
this.dependencyComparator = otherListableFactory.dependencyComparator;
|
||||
// A clone of the AutowireCandidateResolver since it is potentially BeanFactoryAware
|
||||
setAutowireCandidateResolver(otherListableFactory.getAutowireCandidateResolver().cloneIfNecessary());
|
||||
@@ -954,6 +977,32 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return super.obtainInstanceFromSupplier(supplier, beanName, mbd);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args) {
|
||||
super.checkMergedBeanDefinition(mbd, beanName, args);
|
||||
|
||||
if (mbd.isBackgroundInit()) {
|
||||
if (this.preInstantiationThread.get() == PreInstantiation.MAIN && getBootstrapExecutor() != null) {
|
||||
throw new BeanCurrentlyInCreationException(beanName, "Bean marked for background " +
|
||||
"initialization but requested in mainline thread - declare ObjectProvider " +
|
||||
"or lazy injection point in dependent mainline beans");
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Bean intended to be initialized in main bootstrap thread
|
||||
if (this.preInstantiationThread.get() == PreInstantiation.BACKGROUND) {
|
||||
throw new BeanCurrentlyInCreationException(beanName, "Bean marked for mainline initialization " +
|
||||
"but requested in background thread - enforce early instantiation in mainline thread " +
|
||||
"through depends-on '" + beanName + "' declaration for dependent background beans");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCurrentThreadAllowedToHoldSingletonLock() {
|
||||
return (this.preInstantiationThread.get() != PreInstantiation.BACKGROUND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preInstantiateSingletons() throws BeansException {
|
||||
if (logger.isTraceEnabled()) {
|
||||
@@ -965,24 +1014,34 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
|
||||
|
||||
// Trigger initialization of all non-lazy singleton beans...
|
||||
for (String beanName : beanNames) {
|
||||
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
|
||||
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
|
||||
if (isFactoryBean(beanName)) {
|
||||
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
|
||||
if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
|
||||
getBean(beanName);
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||
this.preInstantiationThread.set(PreInstantiation.MAIN);
|
||||
try {
|
||||
for (String beanName : beanNames) {
|
||||
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
|
||||
if (!mbd.isAbstract() && mbd.isSingleton()) {
|
||||
CompletableFuture<?> future = preInstantiateSingleton(beanName, mbd);
|
||||
if (future != null) {
|
||||
futures.add(future);
|
||||
}
|
||||
}
|
||||
else {
|
||||
getBean(beanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.preInstantiationThread.set(null);
|
||||
}
|
||||
if (!futures.isEmpty()) {
|
||||
try {
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join();
|
||||
}
|
||||
catch (CompletionException ex) {
|
||||
ReflectionUtils.rethrowRuntimeException(ex.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger post-initialization callback for all applicable beans...
|
||||
for (String beanName : beanNames) {
|
||||
Object singletonInstance = getSingleton(beanName);
|
||||
Object singletonInstance = getSingleton(beanName, false);
|
||||
if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
|
||||
StartupStep smartInitialize = getApplicationStartup().start("spring.beans.smart-initialize")
|
||||
.tag("beanName", beanName);
|
||||
@@ -992,6 +1051,69 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CompletableFuture<?> preInstantiateSingleton(String beanName, RootBeanDefinition mbd) {
|
||||
if (mbd.isBackgroundInit()) {
|
||||
Executor executor = getBootstrapExecutor();
|
||||
if (executor != null) {
|
||||
String[] dependsOn = mbd.getDependsOn();
|
||||
if (dependsOn != null) {
|
||||
for (String dep : dependsOn) {
|
||||
getBean(dep);
|
||||
}
|
||||
}
|
||||
CompletableFuture<?> future = CompletableFuture.runAsync(
|
||||
() -> instantiateSingletonInBackgroundThread(beanName), executor);
|
||||
addSingletonFactory(beanName, () -> {
|
||||
try {
|
||||
future.join();
|
||||
}
|
||||
catch (CompletionException ex) {
|
||||
ReflectionUtils.rethrowRuntimeException(ex.getCause());
|
||||
}
|
||||
return future; // not to be exposed, just to lead to ClassCastException in case of mismatch
|
||||
});
|
||||
return (!mbd.isLazyInit() ? future : null);
|
||||
}
|
||||
else if (logger.isInfoEnabled()) {
|
||||
logger.info("Bean '" + beanName + "' marked for background initialization " +
|
||||
"without bootstrap executor configured - falling back to mainline initialization");
|
||||
}
|
||||
}
|
||||
if (!mbd.isLazyInit()) {
|
||||
instantiateSingleton(beanName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void instantiateSingletonInBackgroundThread(String beanName) {
|
||||
this.preInstantiationThread.set(PreInstantiation.BACKGROUND);
|
||||
try {
|
||||
instantiateSingleton(beanName);
|
||||
}
|
||||
catch (RuntimeException | Error ex) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Failed to instantiate singleton bean '" + beanName + "' in background thread", ex);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
finally {
|
||||
this.preInstantiationThread.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void instantiateSingleton(String beanName) {
|
||||
if (isFactoryBean(beanName)) {
|
||||
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
|
||||
if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
|
||||
getBean(beanName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
getBean(beanName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Implementation of BeanDefinitionRegistry interface
|
||||
@@ -2395,4 +2517,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum PreInstantiation {
|
||||
|
||||
MAIN, BACKGROUND;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -125,11 +125,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
Assert.notNull(singletonObject, "Singleton object must not be null");
|
||||
this.singletonLock.lock();
|
||||
try {
|
||||
Object oldObject = this.singletonObjects.get(beanName);
|
||||
if (oldObject != null) {
|
||||
throw new IllegalStateException("Could not register object [" + singletonObject +
|
||||
"] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
|
||||
}
|
||||
addSingleton(beanName, singletonObject);
|
||||
}
|
||||
finally {
|
||||
@@ -144,7 +139,11 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
* @param singletonObject the singleton object
|
||||
*/
|
||||
protected void addSingleton(String beanName, Object singletonObject) {
|
||||
this.singletonObjects.put(beanName, singletonObject);
|
||||
Object oldObject = this.singletonObjects.putIfAbsent(beanName, singletonObject);
|
||||
if (oldObject != null) {
|
||||
throw new IllegalStateException("Could not register object [" + singletonObject +
|
||||
"] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
|
||||
}
|
||||
this.singletonFactories.remove(beanName);
|
||||
this.earlySingletonObjects.remove(beanName);
|
||||
this.registeredSingletons.add(beanName);
|
||||
@@ -181,17 +180,17 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
*/
|
||||
@Nullable
|
||||
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
|
||||
// Quick check for existing instance without full singleton lock
|
||||
// Quick check for existing instance without full singleton lock.
|
||||
Object singletonObject = this.singletonObjects.get(beanName);
|
||||
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
|
||||
singletonObject = this.earlySingletonObjects.get(beanName);
|
||||
if (singletonObject == null && allowEarlyReference) {
|
||||
if (!this.singletonLock.tryLock()) {
|
||||
// Avoid early singleton inference outside of original creation thread
|
||||
// Avoid early singleton inference outside of original creation thread.
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// Consistent creation of early reference within full singleton lock
|
||||
// Consistent creation of early reference within full singleton lock.
|
||||
singletonObject = this.singletonObjects.get(beanName);
|
||||
if (singletonObject == null) {
|
||||
singletonObject = this.earlySingletonObjects.get(beanName);
|
||||
@@ -199,8 +198,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
|
||||
if (singletonFactory != null) {
|
||||
singletonObject = singletonFactory.getObject();
|
||||
this.earlySingletonObjects.put(beanName, singletonObject);
|
||||
this.singletonFactories.remove(beanName);
|
||||
// Singleton could have been added or removed in the meantime.
|
||||
if (this.singletonFactories.remove(beanName) != null) {
|
||||
this.earlySingletonObjects.put(beanName, singletonObject);
|
||||
}
|
||||
else {
|
||||
singletonObject = this.singletonObjects.get(beanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,30 +228,36 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
|
||||
Assert.notNull(beanName, "Bean name must not be null");
|
||||
|
||||
boolean locked = this.singletonLock.tryLock();
|
||||
boolean acquireLock = isCurrentThreadAllowedToHoldSingletonLock();
|
||||
boolean locked = (acquireLock && this.singletonLock.tryLock());
|
||||
try {
|
||||
Object singletonObject = this.singletonObjects.get(beanName);
|
||||
if (singletonObject == null) {
|
||||
if (locked) {
|
||||
this.singletonCreationThread = Thread.currentThread();
|
||||
}
|
||||
else {
|
||||
Thread otherThread = this.singletonCreationThread;
|
||||
if (otherThread != null) {
|
||||
if (acquireLock) {
|
||||
if (locked) {
|
||||
this.singletonCreationThread = Thread.currentThread();
|
||||
}
|
||||
else {
|
||||
Thread threadWithLock = this.singletonCreationThread;
|
||||
// Another thread is busy in a singleton factory callback, potentially blocked.
|
||||
// Fallback as of 6.2: process given singleton bean outside of singleton lock.
|
||||
// Thread-safe exposure is still guaranteed, there is just a risk of collisions
|
||||
// when triggering creation of other beans as dependencies of the current bean.
|
||||
if (logger.isInfoEnabled()) {
|
||||
if (threadWithLock != null && logger.isInfoEnabled()) {
|
||||
logger.info("Creating singleton bean '" + beanName + "' in thread \"" +
|
||||
Thread.currentThread().getName() + "\" while thread \"" + otherThread.getName() +
|
||||
Thread.currentThread().getName() + "\" while thread \"" + threadWithLock.getName() +
|
||||
"\" holds singleton lock for other beans " + this.singletonsCurrentlyInCreation);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Singleton lock currently held by some other registration method -> wait.
|
||||
this.singletonLock.lock();
|
||||
locked = true;
|
||||
else {
|
||||
// Singleton lock currently held by some other registration method -> wait.
|
||||
this.singletonLock.lock();
|
||||
locked = true;
|
||||
// Singleton object might have possibly appeared in the meantime.
|
||||
singletonObject = this.singletonObjects.get(beanName);
|
||||
if (singletonObject != null) {
|
||||
return singletonObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.singletonsCurrentlyInDestruction) {
|
||||
@@ -305,6 +315,16 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current thread is allowed to hold the singleton lock.
|
||||
* <p>By default, any thread may acquire and hold the singleton lock, except
|
||||
* background threads from {@link DefaultListableBeanFactory#setBootstrapExecutor}.
|
||||
* @since 6.2
|
||||
*/
|
||||
protected boolean isCurrentThreadAllowedToHoldSingletonLock() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an exception that happened to get suppressed during the creation of a
|
||||
* singleton bean instance, e.g. a temporary circular reference resolution problem.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.context;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
@@ -53,6 +54,16 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life
|
||||
*/
|
||||
String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
|
||||
|
||||
/**
|
||||
* The name of the {@link Executor bootstrap executor} bean in the context.
|
||||
* If none is supplied, no background bootstrapping will be active.
|
||||
* @since 6.2
|
||||
* @see java.util.concurrent.Executor
|
||||
* @see org.springframework.core.task.TaskExecutor
|
||||
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setBootstrapExecutor
|
||||
*/
|
||||
String BOOTSTRAP_EXECUTOR_BEAN_NAME = "bootstrapExecutor";
|
||||
|
||||
/**
|
||||
* Name of the ConversionService bean in the factory.
|
||||
* If none is supplied, default conversion rules apply.
|
||||
|
||||
@@ -260,6 +260,20 @@ public @interface Bean {
|
||||
*/
|
||||
boolean defaultCandidate() default true;
|
||||
|
||||
/**
|
||||
* The bootstrap mode for this bean: default is the main pre-instantiation thread
|
||||
* for non-lazy singleton beans and the caller thread for prototype beans.
|
||||
* <p>Set {@link Bootstrap#BACKGROUND} to allow for instantiating this bean on a
|
||||
* background thread. For a non-lazy singleton, a background pre-instantiation
|
||||
* thread can be used then, while still enforcing the completion at the end of
|
||||
* {@link org.springframework.context.ConfigurableApplicationContext#refresh()}.
|
||||
* For a lazy singleton, a background pre-instantiation thread can be used as well
|
||||
* - with completion allowed at a later point, enforcing it when actually accessed.
|
||||
* @since 6.2
|
||||
* @see Lazy
|
||||
*/
|
||||
Bootstrap bootstrap() default Bootstrap.DEFAULT;
|
||||
|
||||
/**
|
||||
* The optional name of a method to call on the bean instance during initialization.
|
||||
* Not commonly used, given that the method may be called programmatically directly
|
||||
@@ -299,4 +313,28 @@ public @interface Bean {
|
||||
*/
|
||||
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
|
||||
|
||||
|
||||
/**
|
||||
* Local enumeration for the bootstrap mode.
|
||||
* @since 6.2
|
||||
* @see #bootstrap()
|
||||
*/
|
||||
enum Bootstrap {
|
||||
|
||||
/**
|
||||
* Constant to indicate the main pre-instantiation thread for non-lazy
|
||||
* singleton beans and the caller thread for prototype beans.
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* Allow for instantiating a bean on a background thread.
|
||||
* <p>For a non-lazy singleton, a background pre-instantiation thread
|
||||
* can be used while still enforcing the completion on context refresh.
|
||||
* For a lazy singleton, a background pre-instantiation thread can be used
|
||||
* with completion allowed at a later point (when actually accessed).
|
||||
*/
|
||||
BACKGROUND,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -251,6 +251,11 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
beanDef.setDefaultCandidate(false);
|
||||
}
|
||||
|
||||
Bean.Bootstrap instantiation = bean.getEnum("bootstrap");
|
||||
if (instantiation == Bean.Bootstrap.BACKGROUND) {
|
||||
beanDef.setBackgroundInit(true);
|
||||
}
|
||||
|
||||
String initMethodName = bean.getString("initMethod");
|
||||
if (StringUtils.hasText(initMethodName)) {
|
||||
beanDef.setInitMethodName(initMethodName);
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -137,17 +138,6 @@ import org.springframework.util.ReflectionUtils;
|
||||
public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
implements ConfigurableApplicationContext {
|
||||
|
||||
/**
|
||||
* The name of the {@link LifecycleProcessor} bean in the context.
|
||||
* If none is supplied, a {@link DefaultLifecycleProcessor} is used.
|
||||
* @since 3.0
|
||||
* @see org.springframework.context.LifecycleProcessor
|
||||
* @see org.springframework.context.support.DefaultLifecycleProcessor
|
||||
* @see #start()
|
||||
* @see #stop()
|
||||
*/
|
||||
public static final String LIFECYCLE_PROCESSOR_BEAN_NAME = "lifecycleProcessor";
|
||||
|
||||
/**
|
||||
* The name of the {@link MessageSource} bean in the context.
|
||||
* If none is supplied, message resolution is delegated to the parent.
|
||||
@@ -168,6 +158,17 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
*/
|
||||
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
|
||||
|
||||
/**
|
||||
* The name of the {@link LifecycleProcessor} bean in the context.
|
||||
* If none is supplied, a {@link DefaultLifecycleProcessor} is used.
|
||||
* @since 3.0
|
||||
* @see org.springframework.context.LifecycleProcessor
|
||||
* @see org.springframework.context.support.DefaultLifecycleProcessor
|
||||
* @see #start()
|
||||
* @see #stop()
|
||||
*/
|
||||
public static final String LIFECYCLE_PROCESSOR_BEAN_NAME = "lifecycleProcessor";
|
||||
|
||||
|
||||
static {
|
||||
// Eagerly load the ContextClosedEvent class to avoid weird classloader issues
|
||||
@@ -806,8 +807,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the MessageSource.
|
||||
* Use parent's if none defined in this context.
|
||||
* Initialize the {@link MessageSource}.
|
||||
* <p>Uses parent's {@code MessageSource} if none defined in this context.
|
||||
* @see #MESSAGE_SOURCE_BEAN_NAME
|
||||
*/
|
||||
protected void initMessageSource() {
|
||||
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
|
||||
@@ -837,8 +839,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the ApplicationEventMulticaster.
|
||||
* Uses SimpleApplicationEventMulticaster if none defined in the context.
|
||||
* Initialize the {@link ApplicationEventMulticaster}.
|
||||
* <p>Uses {@link SimpleApplicationEventMulticaster} if none defined in the context.
|
||||
* @see #APPLICATION_EVENT_MULTICASTER_BEAN_NAME
|
||||
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
|
||||
*/
|
||||
protected void initApplicationEventMulticaster() {
|
||||
@@ -861,15 +864,16 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the LifecycleProcessor.
|
||||
* Uses DefaultLifecycleProcessor if none defined in the context.
|
||||
* Initialize the {@link LifecycleProcessor}.
|
||||
* <p>Uses {@link DefaultLifecycleProcessor} if none defined in the context.
|
||||
* @since 3.0
|
||||
* @see #LIFECYCLE_PROCESSOR_BEAN_NAME
|
||||
* @see org.springframework.context.support.DefaultLifecycleProcessor
|
||||
*/
|
||||
protected void initLifecycleProcessor() {
|
||||
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
|
||||
if (beanFactory.containsLocalBean(LIFECYCLE_PROCESSOR_BEAN_NAME)) {
|
||||
this.lifecycleProcessor =
|
||||
beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);
|
||||
this.lifecycleProcessor = beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Using LifecycleProcessor [" + this.lifecycleProcessor + "]");
|
||||
}
|
||||
@@ -929,6 +933,13 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
* initializing all remaining singleton beans.
|
||||
*/
|
||||
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
|
||||
// Initialize bootstrap executor for this context.
|
||||
if (beanFactory.containsBean(BOOTSTRAP_EXECUTOR_BEAN_NAME) &&
|
||||
beanFactory.isTypeMatch(BOOTSTRAP_EXECUTOR_BEAN_NAME, Executor.class)) {
|
||||
beanFactory.setBootstrapExecutor(
|
||||
beanFactory.getBean(BOOTSTRAP_EXECUTOR_BEAN_NAME, Executor.class));
|
||||
}
|
||||
|
||||
// Initialize conversion service for this context.
|
||||
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
|
||||
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.testfixture.EnabledForTestGroups;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import static org.springframework.context.annotation.Bean.Bootstrap.BACKGROUND;
|
||||
import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.2
|
||||
*/
|
||||
class BackgroundBootstrapTests {
|
||||
|
||||
@Test
|
||||
@Timeout(5)
|
||||
@EnabledForTestGroups(LONG_RUNNING)
|
||||
void bootstrapWithCustomExecutor() {
|
||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CustomExecutorBeanConfig.class);
|
||||
ctx.getBean("testBean1", TestBean.class);
|
||||
ctx.getBean("testBean2", TestBean.class);
|
||||
ctx.getBean("testBean3", TestBean.class);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class CustomExecutorBeanConfig {
|
||||
|
||||
@Bean
|
||||
public ThreadPoolTaskExecutor bootstrapExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setThreadNamePrefix("Custom-");
|
||||
executor.setCorePoolSize(2);
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Bean(bootstrap = BACKGROUND) @DependsOn("testBean3")
|
||||
public TestBean testBean1(TestBean testBean3) throws InterruptedException{
|
||||
Thread.sleep(3000);
|
||||
return new TestBean();
|
||||
}
|
||||
|
||||
@Bean(bootstrap = BACKGROUND) @Lazy
|
||||
public TestBean testBean2() throws InterruptedException {
|
||||
Thread.sleep(3000);
|
||||
return new TestBean();
|
||||
}
|
||||
|
||||
@Bean @Lazy
|
||||
public TestBean testBean3() {
|
||||
return new TestBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public String dependent(@Lazy TestBean testBean1, @Lazy TestBean testBean2, @Lazy TestBean testBean3) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user