Introduce background bootstrapping for individual singleton beans

Closes gh-13410
Closes gh-19487
See gh-23501
This commit is contained in:
Juergen Hoeller
2024-02-27 22:33:18 +01:00
parent 9e7c64270b
commit 17b2087198
11 changed files with 410 additions and 64 deletions

View File

@@ -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?");
}
/**

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");
* 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.

View File

@@ -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}.

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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,
}
}

View File

@@ -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);

View File

@@ -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)) {

View File

@@ -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 "";
}
}
}