Revise singleton registry for lenient locking (fallback instead of deadlock)
Closes gh-23501
This commit is contained in:
parent
f529386ce2
commit
902e5707a8
|
@ -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");
|
* 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.
|
||||||
|
@ -109,13 +109,8 @@ public class BeanFactoryAspectInstanceFactory implements MetadataAwareAspectInst
|
||||||
// Rely on singleton semantics provided by the factory -> no local lock.
|
// Rely on singleton semantics provided by the factory -> no local lock.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else if (this.beanFactory instanceof ConfigurableBeanFactory cbf) {
|
|
||||||
// No singleton guarantees from the factory -> let's lock locally but
|
|
||||||
// reuse the factory's singleton lock, just in case a lazy dependency
|
|
||||||
// of our advice bean happens to trigger the singleton lock implicitly...
|
|
||||||
return cbf.getSingletonMutex();
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
|
// No singleton guarantees from the factory -> let's lock locally.
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* 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.
|
||||||
|
@ -23,7 +23,6 @@ import org.aopalliance.aop.Advice;
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
@ -52,7 +51,7 @@ public abstract class AbstractBeanFactoryPointcutAdvisor extends AbstractPointcu
|
||||||
@Nullable
|
@Nullable
|
||||||
private transient volatile Advice advice;
|
private transient volatile Advice advice;
|
||||||
|
|
||||||
private transient volatile Object adviceMonitor = new Object();
|
private transient Object adviceMonitor = new Object();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,16 +77,6 @@ public abstract class AbstractBeanFactoryPointcutAdvisor extends AbstractPointcu
|
||||||
@Override
|
@Override
|
||||||
public void setBeanFactory(BeanFactory beanFactory) {
|
public void setBeanFactory(BeanFactory beanFactory) {
|
||||||
this.beanFactory = beanFactory;
|
this.beanFactory = beanFactory;
|
||||||
resetAdviceMonitor();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetAdviceMonitor() {
|
|
||||||
if (this.beanFactory instanceof ConfigurableBeanFactory cbf) {
|
|
||||||
this.adviceMonitor = cbf.getSingletonMutex();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.adviceMonitor = new Object();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,9 +107,7 @@ public abstract class AbstractBeanFactoryPointcutAdvisor extends AbstractPointcu
|
||||||
return advice;
|
return advice;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// No singleton guarantees from the factory -> let's lock locally but
|
// No singleton guarantees from the factory -> let's lock locally.
|
||||||
// reuse the factory's singleton lock, just in case a lazy dependency
|
|
||||||
// of our advice bean happens to trigger the singleton lock implicitly...
|
|
||||||
synchronized (this.adviceMonitor) {
|
synchronized (this.adviceMonitor) {
|
||||||
advice = this.advice;
|
advice = this.advice;
|
||||||
if (advice == null) {
|
if (advice == null) {
|
||||||
|
@ -155,7 +142,7 @@ public abstract class AbstractBeanFactoryPointcutAdvisor extends AbstractPointcu
|
||||||
ois.defaultReadObject();
|
ois.defaultReadObject();
|
||||||
|
|
||||||
// Initialize transient fields.
|
// Initialize transient fields.
|
||||||
resetAdviceMonitor();
|
this.adviceMonitor = new Object();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 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.
|
||||||
|
@ -129,7 +129,10 @@ public interface SingletonBeanRegistry {
|
||||||
* Return the singleton mutex used by this registry (for external collaborators).
|
* Return the singleton mutex used by this registry (for external collaborators).
|
||||||
* @return the mutex object (never {@code null})
|
* @return the mutex object (never {@code null})
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
|
* @deprecated as of 6.2, in favor of lenient singleton locking
|
||||||
|
* (with this method returning an arbitrary object to lock on)
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.2")
|
||||||
Object getSingletonMutex();
|
Object getSingletonMutex();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -972,7 +972,6 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
|
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
|
||||||
synchronized (getSingletonMutex()) {
|
|
||||||
BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
|
BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
|
||||||
if (bw != null) {
|
if (bw != null) {
|
||||||
return (FactoryBean<?>) bw.getWrappedInstance();
|
return (FactoryBean<?>) bw.getWrappedInstance();
|
||||||
|
@ -995,6 +994,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
bw = createBeanInstance(beanName, mbd, null);
|
bw = createBeanInstance(beanName, mbd, null);
|
||||||
instance = bw.getWrappedInstance();
|
instance = bw.getWrappedInstance();
|
||||||
|
this.factoryBeanInstanceCache.put(beanName, bw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (UnsatisfiedDependencyException ex) {
|
catch (UnsatisfiedDependencyException ex) {
|
||||||
|
@ -1019,12 +1019,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||||
afterSingletonCreation(beanName);
|
afterSingletonCreation(beanName);
|
||||||
}
|
}
|
||||||
|
|
||||||
FactoryBean<?> fb = getFactoryBean(beanName, instance);
|
return getFactoryBean(beanName, instance);
|
||||||
if (bw != null) {
|
|
||||||
this.factoryBeanInstanceCache.put(beanName, bw);
|
|
||||||
}
|
|
||||||
return fb;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1912,22 +1907,18 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void removeSingleton(String beanName) {
|
protected void removeSingleton(String beanName) {
|
||||||
synchronized (getSingletonMutex()) {
|
|
||||||
super.removeSingleton(beanName);
|
super.removeSingleton(beanName);
|
||||||
this.factoryBeanInstanceCache.remove(beanName);
|
this.factoryBeanInstanceCache.remove(beanName);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overridden to clear FactoryBean instance cache as well.
|
* Overridden to clear FactoryBean instance cache as well.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void clearSingletonCache() {
|
protected void clearSingletonCache() {
|
||||||
synchronized (getSingletonMutex()) {
|
|
||||||
super.clearSingletonCache();
|
super.clearSingletonCache();
|
||||||
this.factoryBeanInstanceCache.clear();
|
this.factoryBeanInstanceCache.clear();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose the logger to collaborating delegates.
|
* Expose the logger to collaborating delegates.
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -25,6 +25,8 @@ import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.ReentrantLock;
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanCreationException;
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
import org.springframework.beans.factory.BeanCreationNotAllowedException;
|
import org.springframework.beans.factory.BeanCreationNotAllowedException;
|
||||||
|
@ -84,7 +86,9 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
|
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
|
||||||
|
|
||||||
/** Set of registered singletons, containing the bean names in registration order. */
|
/** Set of registered singletons, containing the bean names in registration order. */
|
||||||
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
|
private final Set<String> registeredSingletons = Collections.synchronizedSet(new LinkedHashSet<>(256));
|
||||||
|
|
||||||
|
private final Lock singletonLock = new ReentrantLock();
|
||||||
|
|
||||||
/** Names of beans that are currently in creation. */
|
/** Names of beans that are currently in creation. */
|
||||||
private final Set<String> singletonsCurrentlyInCreation =
|
private final Set<String> singletonsCurrentlyInCreation =
|
||||||
|
@ -94,6 +98,9 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
private final Set<String> inCreationCheckExclusions =
|
private final Set<String> inCreationCheckExclusions =
|
||||||
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
|
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private volatile Thread singletonCreationThread;
|
||||||
|
|
||||||
/** Collection of suppressed Exceptions, available for associating related causes. */
|
/** Collection of suppressed Exceptions, available for associating related causes. */
|
||||||
@Nullable
|
@Nullable
|
||||||
private Set<Exception> suppressedExceptions;
|
private Set<Exception> suppressedExceptions;
|
||||||
|
@ -118,7 +125,8 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
|
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
|
||||||
Assert.notNull(beanName, "Bean name must not be null");
|
Assert.notNull(beanName, "Bean name must not be null");
|
||||||
Assert.notNull(singletonObject, "Singleton object must not be null");
|
Assert.notNull(singletonObject, "Singleton object must not be null");
|
||||||
synchronized (this.singletonObjects) {
|
this.singletonLock.lock();
|
||||||
|
try {
|
||||||
Object oldObject = this.singletonObjects.get(beanName);
|
Object oldObject = this.singletonObjects.get(beanName);
|
||||||
if (oldObject != null) {
|
if (oldObject != null) {
|
||||||
throw new IllegalStateException("Could not register object [" + singletonObject +
|
throw new IllegalStateException("Could not register object [" + singletonObject +
|
||||||
|
@ -126,6 +134,9 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
}
|
}
|
||||||
addSingleton(beanName, singletonObject);
|
addSingleton(beanName, singletonObject);
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
this.singletonLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,13 +146,11 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
* @param singletonObject the singleton object
|
* @param singletonObject the singleton object
|
||||||
*/
|
*/
|
||||||
protected void addSingleton(String beanName, Object singletonObject) {
|
protected void addSingleton(String beanName, Object singletonObject) {
|
||||||
synchronized (this.singletonObjects) {
|
|
||||||
this.singletonObjects.put(beanName, singletonObject);
|
this.singletonObjects.put(beanName, singletonObject);
|
||||||
this.singletonFactories.remove(beanName);
|
this.singletonFactories.remove(beanName);
|
||||||
this.earlySingletonObjects.remove(beanName);
|
this.earlySingletonObjects.remove(beanName);
|
||||||
this.registeredSingletons.add(beanName);
|
this.registeredSingletons.add(beanName);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the given singleton factory for building the specified singleton
|
* Add the given singleton factory for building the specified singleton
|
||||||
|
@ -153,14 +162,10 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
*/
|
*/
|
||||||
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
|
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
|
||||||
Assert.notNull(singletonFactory, "Singleton factory must not be null");
|
Assert.notNull(singletonFactory, "Singleton factory must not be null");
|
||||||
synchronized (this.singletonObjects) {
|
|
||||||
if (!this.singletonObjects.containsKey(beanName)) {
|
|
||||||
this.singletonFactories.put(beanName, singletonFactory);
|
this.singletonFactories.put(beanName, singletonFactory);
|
||||||
this.earlySingletonObjects.remove(beanName);
|
this.earlySingletonObjects.remove(beanName);
|
||||||
this.registeredSingletons.add(beanName);
|
this.registeredSingletons.add(beanName);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -183,7 +188,8 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
|
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
|
||||||
singletonObject = this.earlySingletonObjects.get(beanName);
|
singletonObject = this.earlySingletonObjects.get(beanName);
|
||||||
if (singletonObject == null && allowEarlyReference) {
|
if (singletonObject == null && allowEarlyReference) {
|
||||||
synchronized (this.singletonObjects) {
|
this.singletonLock.lock();
|
||||||
|
try {
|
||||||
// Consistent creation of early reference within full singleton lock
|
// Consistent creation of early reference within full singleton lock
|
||||||
singletonObject = this.singletonObjects.get(beanName);
|
singletonObject = this.singletonObjects.get(beanName);
|
||||||
if (singletonObject == null) {
|
if (singletonObject == null) {
|
||||||
|
@ -198,6 +204,9 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
this.singletonLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return singletonObject;
|
return singletonObject;
|
||||||
|
@ -213,9 +222,33 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
*/
|
*/
|
||||||
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
|
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
|
||||||
Assert.notNull(beanName, "Bean name must not be null");
|
Assert.notNull(beanName, "Bean name must not be null");
|
||||||
synchronized (this.singletonObjects) {
|
|
||||||
|
boolean locked = this.singletonLock.tryLock();
|
||||||
|
try {
|
||||||
Object singletonObject = this.singletonObjects.get(beanName);
|
Object singletonObject = this.singletonObjects.get(beanName);
|
||||||
if (singletonObject == null) {
|
if (singletonObject == null) {
|
||||||
|
if (locked) {
|
||||||
|
this.singletonCreationThread = Thread.currentThread();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Thread otherThread = this.singletonCreationThread;
|
||||||
|
if (otherThread != null) {
|
||||||
|
// 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()) {
|
||||||
|
logger.info("Creating singleton bean '" + beanName + "' in thread \"" +
|
||||||
|
Thread.currentThread().getName() + "\" while thread \"" + otherThread.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
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 " +
|
||||||
|
@ -226,10 +259,11 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
}
|
}
|
||||||
beforeSingletonCreation(beanName);
|
beforeSingletonCreation(beanName);
|
||||||
boolean newSingleton = false;
|
boolean newSingleton = false;
|
||||||
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
|
boolean recordSuppressedExceptions = (locked && this.suppressedExceptions == null);
|
||||||
if (recordSuppressedExceptions) {
|
if (recordSuppressedExceptions) {
|
||||||
this.suppressedExceptions = new LinkedHashSet<>();
|
this.suppressedExceptions = new LinkedHashSet<>();
|
||||||
}
|
}
|
||||||
|
this.singletonCreationThread = Thread.currentThread();
|
||||||
try {
|
try {
|
||||||
singletonObject = singletonFactory.getObject();
|
singletonObject = singletonFactory.getObject();
|
||||||
newSingleton = true;
|
newSingleton = true;
|
||||||
|
@ -251,6 +285,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
this.singletonCreationThread = null;
|
||||||
if (recordSuppressedExceptions) {
|
if (recordSuppressedExceptions) {
|
||||||
this.suppressedExceptions = null;
|
this.suppressedExceptions = null;
|
||||||
}
|
}
|
||||||
|
@ -262,6 +297,11 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
}
|
}
|
||||||
return singletonObject;
|
return singletonObject;
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
if (locked) {
|
||||||
|
this.singletonLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -274,27 +314,22 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
* @see BeanCreationException#getRelatedCauses()
|
* @see BeanCreationException#getRelatedCauses()
|
||||||
*/
|
*/
|
||||||
protected void onSuppressedException(Exception ex) {
|
protected void onSuppressedException(Exception ex) {
|
||||||
synchronized (this.singletonObjects) {
|
|
||||||
if (this.suppressedExceptions != null && this.suppressedExceptions.size() < SUPPRESSED_EXCEPTIONS_LIMIT) {
|
if (this.suppressedExceptions != null && this.suppressedExceptions.size() < SUPPRESSED_EXCEPTIONS_LIMIT) {
|
||||||
this.suppressedExceptions.add(ex);
|
this.suppressedExceptions.add(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the bean with the given name from the singleton cache of this factory,
|
* Remove the bean with the given name from the singleton cache of this factory,
|
||||||
* to be able to clean up eager registration of a singleton if creation failed.
|
* to be able to clean up eager registration of a singleton if creation failed.
|
||||||
* @param beanName the name of the bean
|
* @param beanName the name of the bean
|
||||||
* @see #getSingletonMutex()
|
|
||||||
*/
|
*/
|
||||||
protected void removeSingleton(String beanName) {
|
protected void removeSingleton(String beanName) {
|
||||||
synchronized (this.singletonObjects) {
|
|
||||||
this.singletonObjects.remove(beanName);
|
this.singletonObjects.remove(beanName);
|
||||||
this.singletonFactories.remove(beanName);
|
this.singletonFactories.remove(beanName);
|
||||||
this.earlySingletonObjects.remove(beanName);
|
this.earlySingletonObjects.remove(beanName);
|
||||||
this.registeredSingletons.remove(beanName);
|
this.registeredSingletons.remove(beanName);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsSingleton(String beanName) {
|
public boolean containsSingleton(String beanName) {
|
||||||
|
@ -303,17 +338,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getSingletonNames() {
|
public String[] getSingletonNames() {
|
||||||
synchronized (this.singletonObjects) {
|
|
||||||
return StringUtils.toStringArray(this.registeredSingletons);
|
return StringUtils.toStringArray(this.registeredSingletons);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSingletonCount() {
|
public int getSingletonCount() {
|
||||||
synchronized (this.singletonObjects) {
|
|
||||||
return this.registeredSingletons.size();
|
return this.registeredSingletons.size();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setCurrentlyInCreation(String beanName, boolean inCreation) {
|
public void setCurrentlyInCreation(String beanName, boolean inCreation) {
|
||||||
|
@ -508,9 +539,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Destroying singletons in " + this);
|
logger.trace("Destroying singletons in " + this);
|
||||||
}
|
}
|
||||||
synchronized (this.singletonObjects) {
|
this.singletonLock.lock();
|
||||||
|
try {
|
||||||
this.singletonsCurrentlyInDestruction = true;
|
this.singletonsCurrentlyInDestruction = true;
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
this.singletonLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
String[] disposableBeanNames;
|
String[] disposableBeanNames;
|
||||||
synchronized (this.disposableBeans) {
|
synchronized (this.disposableBeans) {
|
||||||
|
@ -524,22 +559,26 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
this.dependentBeanMap.clear();
|
this.dependentBeanMap.clear();
|
||||||
this.dependenciesForBeanMap.clear();
|
this.dependenciesForBeanMap.clear();
|
||||||
|
|
||||||
|
this.singletonLock.lock();
|
||||||
|
try {
|
||||||
clearSingletonCache();
|
clearSingletonCache();
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
this.singletonLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all cached singleton instances in this registry.
|
* Clear all cached singleton instances in this registry.
|
||||||
* @since 4.3.15
|
* @since 4.3.15
|
||||||
*/
|
*/
|
||||||
protected void clearSingletonCache() {
|
protected void clearSingletonCache() {
|
||||||
synchronized (this.singletonObjects) {
|
|
||||||
this.singletonObjects.clear();
|
this.singletonObjects.clear();
|
||||||
this.singletonFactories.clear();
|
this.singletonFactories.clear();
|
||||||
this.earlySingletonObjects.clear();
|
this.earlySingletonObjects.clear();
|
||||||
this.registeredSingletons.clear();
|
this.registeredSingletons.clear();
|
||||||
this.singletonsCurrentlyInDestruction = false;
|
this.singletonsCurrentlyInDestruction = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy the given bean. Delegates to {@code destroyBean}
|
* Destroy the given bean. Delegates to {@code destroyBean}
|
||||||
|
@ -549,7 +588,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
*/
|
*/
|
||||||
public void destroySingleton(String beanName) {
|
public void destroySingleton(String beanName) {
|
||||||
// Remove a registered singleton of the given name, if any.
|
// Remove a registered singleton of the given name, if any.
|
||||||
|
this.singletonLock.lock();
|
||||||
|
try {
|
||||||
removeSingleton(beanName);
|
removeSingleton(beanName);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.singletonLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
// Destroy the corresponding DisposableBean instance.
|
// Destroy the corresponding DisposableBean instance.
|
||||||
DisposableBean disposableBean;
|
DisposableBean disposableBean;
|
||||||
|
@ -621,16 +666,10 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
this.dependenciesForBeanMap.remove(beanName);
|
this.dependenciesForBeanMap.remove(beanName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Deprecated(since = "6.2")
|
||||||
* Exposes the singleton mutex to subclasses and external collaborators.
|
|
||||||
* <p>Subclasses should synchronize on the given Object if they perform
|
|
||||||
* any sort of extended singleton creation phase. In particular, subclasses
|
|
||||||
* should <i>not</i> have their own mutexes involved in singleton creation,
|
|
||||||
* to avoid the potential for deadlocks in lazy-init situations.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public final Object getSingletonMutex() {
|
public final Object getSingletonMutex() {
|
||||||
return this.singletonObjects;
|
return new Object();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -118,7 +118,6 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||||
*/
|
*/
|
||||||
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
|
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
|
||||||
if (factory.isSingleton() && containsSingleton(beanName)) {
|
if (factory.isSingleton() && containsSingleton(beanName)) {
|
||||||
synchronized (getSingletonMutex()) {
|
|
||||||
Object object = this.factoryBeanObjectCache.get(beanName);
|
Object object = this.factoryBeanObjectCache.get(beanName);
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
object = doGetObjectFromFactoryBean(factory, beanName);
|
object = doGetObjectFromFactoryBean(factory, beanName);
|
||||||
|
@ -131,7 +130,7 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||||
else {
|
else {
|
||||||
if (shouldPostProcess) {
|
if (shouldPostProcess) {
|
||||||
if (isSingletonCurrentlyInCreation(beanName)) {
|
if (isSingletonCurrentlyInCreation(beanName)) {
|
||||||
// Temporarily return non-post-processed object, not storing it yet..
|
// Temporarily return non-post-processed object, not storing it yet
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
beforeSingletonCreation(beanName);
|
beforeSingletonCreation(beanName);
|
||||||
|
@ -153,7 +152,6 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||||
}
|
}
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
Object object = doGetObjectFromFactoryBean(factory, beanName);
|
Object object = doGetObjectFromFactoryBean(factory, beanName);
|
||||||
if (shouldPostProcess) {
|
if (shouldPostProcess) {
|
||||||
|
@ -234,21 +232,17 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void removeSingleton(String beanName) {
|
protected void removeSingleton(String beanName) {
|
||||||
synchronized (getSingletonMutex()) {
|
|
||||||
super.removeSingleton(beanName);
|
super.removeSingleton(beanName);
|
||||||
this.factoryBeanObjectCache.remove(beanName);
|
this.factoryBeanObjectCache.remove(beanName);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overridden to clear the FactoryBean object cache as well.
|
* Overridden to clear the FactoryBean object cache as well.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void clearSingletonCache() {
|
protected void clearSingletonCache() {
|
||||||
synchronized (getSingletonMutex()) {
|
|
||||||
super.clearSingletonCache();
|
super.clearSingletonCache();
|
||||||
this.factoryBeanObjectCache.clear();
|
this.factoryBeanObjectCache.clear();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* 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.beans.factory;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
|
import org.springframework.beans.testfixture.beans.TestBean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Juergen Hoeller
|
||||||
|
* @since 6.2
|
||||||
|
*/
|
||||||
|
class BeanFactoryLockingTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fallbackForThreadDuringInitialization() {
|
||||||
|
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||||
|
beanFactory.registerBeanDefinition("bean1", new RootBeanDefinition(ThreadDuringInitialization.class));
|
||||||
|
beanFactory.registerBeanDefinition("bean2", new RootBeanDefinition(TestBean.class));
|
||||||
|
beanFactory.getBean(ThreadDuringInitialization.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static class ThreadDuringInitialization implements BeanFactoryAware, InitializingBean {
|
||||||
|
|
||||||
|
private BeanFactory beanFactory;
|
||||||
|
|
||||||
|
private volatile boolean initialized;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBeanFactory(BeanFactory beanFactory) {
|
||||||
|
this.beanFactory = beanFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
Thread thread = new Thread(() -> {
|
||||||
|
beanFactory.getBean(TestBean.class);
|
||||||
|
initialized = true;
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
thread.join();
|
||||||
|
if (!initialized) {
|
||||||
|
throw new IllegalStateException("Thread not executed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.
|
||||||
|
@ -22,7 +22,6 @@ import java.util.List;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
|
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
|
||||||
import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
|
import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
|
||||||
|
@ -57,8 +56,6 @@ public class JmsListenerEndpointRegistrar implements BeanFactoryAware, Initializ
|
||||||
|
|
||||||
private boolean startImmediately;
|
private boolean startImmediately;
|
||||||
|
|
||||||
private Object mutex = this.endpointDescriptors;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the {@link JmsListenerEndpointRegistry} instance to use.
|
* Set the {@link JmsListenerEndpointRegistry} instance to use.
|
||||||
|
@ -124,9 +121,6 @@ public class JmsListenerEndpointRegistrar implements BeanFactoryAware, Initializ
|
||||||
@Override
|
@Override
|
||||||
public void setBeanFactory(BeanFactory beanFactory) {
|
public void setBeanFactory(BeanFactory beanFactory) {
|
||||||
this.beanFactory = beanFactory;
|
this.beanFactory = beanFactory;
|
||||||
if (beanFactory instanceof ConfigurableBeanFactory cbf) {
|
|
||||||
this.mutex = cbf.getSingletonMutex();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,14 +131,12 @@ public class JmsListenerEndpointRegistrar implements BeanFactoryAware, Initializ
|
||||||
|
|
||||||
protected void registerAllEndpoints() {
|
protected void registerAllEndpoints() {
|
||||||
Assert.state(this.endpointRegistry != null, "No JmsListenerEndpointRegistry set");
|
Assert.state(this.endpointRegistry != null, "No JmsListenerEndpointRegistry set");
|
||||||
synchronized (this.mutex) {
|
|
||||||
for (JmsListenerEndpointDescriptor descriptor : this.endpointDescriptors) {
|
for (JmsListenerEndpointDescriptor descriptor : this.endpointDescriptors) {
|
||||||
this.endpointRegistry.registerListenerContainer(
|
this.endpointRegistry.registerListenerContainer(
|
||||||
descriptor.endpoint, resolveContainerFactory(descriptor));
|
descriptor.endpoint, resolveContainerFactory(descriptor));
|
||||||
}
|
}
|
||||||
this.startImmediately = true; // trigger immediate startup
|
this.startImmediately = true; // trigger immediate startup
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private JmsListenerContainerFactory<?> resolveContainerFactory(JmsListenerEndpointDescriptor descriptor) {
|
private JmsListenerContainerFactory<?> resolveContainerFactory(JmsListenerEndpointDescriptor descriptor) {
|
||||||
if (descriptor.containerFactory != null) {
|
if (descriptor.containerFactory != null) {
|
||||||
|
@ -180,7 +172,6 @@ public class JmsListenerEndpointRegistrar implements BeanFactoryAware, Initializ
|
||||||
// Factory may be null, we defer the resolution right before actually creating the container
|
// Factory may be null, we defer the resolution right before actually creating the container
|
||||||
JmsListenerEndpointDescriptor descriptor = new JmsListenerEndpointDescriptor(endpoint, factory);
|
JmsListenerEndpointDescriptor descriptor = new JmsListenerEndpointDescriptor(endpoint, factory);
|
||||||
|
|
||||||
synchronized (this.mutex) {
|
|
||||||
if (this.startImmediately) { // register and start immediately
|
if (this.startImmediately) { // register and start immediately
|
||||||
Assert.state(this.endpointRegistry != null, "No JmsListenerEndpointRegistry set");
|
Assert.state(this.endpointRegistry != null, "No JmsListenerEndpointRegistry set");
|
||||||
this.endpointRegistry.registerListenerContainer(descriptor.endpoint,
|
this.endpointRegistry.registerListenerContainer(descriptor.endpoint,
|
||||||
|
@ -190,7 +181,6 @@ public class JmsListenerEndpointRegistrar implements BeanFactoryAware, Initializ
|
||||||
this.endpointDescriptors.add(descriptor);
|
this.endpointDescriptors.add(descriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new {@link JmsListenerEndpoint} using the default
|
* Register a new {@link JmsListenerEndpoint} using the default
|
||||||
|
|
Loading…
Reference in New Issue