Fix @Autowired+@PostConstruct+@Configuration issue
A subtle issue existed with the way we relied on isCurrentlyInCreation to determine whether a @Bean method is being called by the container or by user code. This worked in most cases, but in the particular scenario laid out by SPR-8080, this approach was no longer sufficient. This change introduces a ThreadLocal that contains the factory method currently being invoked by the container, such that enhanced @Bean methods can check against it to see if they are being called by the container or not. If so, that is the cue that the user-defined @Bean method implementation should be invoked in order to actually create the bean for the first time. If not, then the cached instance of the already-created bean should be looked up and returned. See ConfigurationClassPostConstructAndAutowiringTests for reproduction cases and more detail. Issue: SPR-8080
This commit is contained in:
@@ -320,6 +320,15 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single
|
||||
*/
|
||||
boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException;
|
||||
|
||||
/**
|
||||
* Explicitly control in-creation status of the specified bean. For
|
||||
* container internal use only.
|
||||
* @param beanName the name of the bean
|
||||
* @param inCreation whether the bean is currently in creation
|
||||
* @since 3.1
|
||||
*/
|
||||
void setCurrentlyInCreation(String beanName, boolean inCreation);
|
||||
|
||||
/**
|
||||
* Determine whether the specified bean is currently in creation.
|
||||
* @param beanName the name of the bean
|
||||
|
||||
@@ -97,6 +97,9 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
/** Names of beans that are currently in creation */
|
||||
private final Set<String> singletonsCurrentlyInCreation = Collections.synchronizedSet(new HashSet<String>());
|
||||
|
||||
/** Names of beans currently excluded from in creation checks */
|
||||
private final Set<String> inCreationCheckExclusions = new HashSet<String>();
|
||||
|
||||
/** List of suppressed Exceptions, available for associating related causes */
|
||||
private Set<Exception> suppressedExceptions;
|
||||
|
||||
@@ -293,7 +296,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
* @see #isSingletonCurrentlyInCreation
|
||||
*/
|
||||
protected void beforeSingletonCreation(String beanName) {
|
||||
if (!this.singletonsCurrentlyInCreation.add(beanName)) {
|
||||
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
|
||||
throw new BeanCurrentlyInCreationException(beanName);
|
||||
}
|
||||
}
|
||||
@@ -305,11 +308,19 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
* @see #isSingletonCurrentlyInCreation
|
||||
*/
|
||||
protected void afterSingletonCreation(String beanName) {
|
||||
if (!this.singletonsCurrentlyInCreation.remove(beanName)) {
|
||||
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
|
||||
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
|
||||
}
|
||||
}
|
||||
|
||||
public final void setCurrentlyInCreation(String beanName, boolean inCreation) {
|
||||
if (!inCreation) {
|
||||
this.inCreationCheckExclusions.add(beanName);
|
||||
} else {
|
||||
this.inCreationCheckExclusions.remove(beanName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the specified singleton bean is currently in creation
|
||||
* (within the entire factory).
|
||||
|
||||
@@ -42,6 +42,8 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
|
||||
private static final ThreadLocal<Method> currentlyInvokedFactoryMethod = new ThreadLocal<Method>();
|
||||
|
||||
public Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) {
|
||||
// Don't override the class with CGLIB if no overrides.
|
||||
if (beanDefinition.getMethodOverrides().isEmpty()) {
|
||||
@@ -140,9 +142,19 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
else {
|
||||
ReflectionUtils.makeAccessible(factoryMethod);
|
||||
}
|
||||
|
||||
// It's a static method if the target is null.
|
||||
return factoryMethod.invoke(factoryBean, args);
|
||||
|
||||
Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get();
|
||||
try {
|
||||
currentlyInvokedFactoryMethod.set(factoryMethod);
|
||||
return factoryMethod.invoke(factoryBean, args);
|
||||
} finally {
|
||||
if (priorInvokedFactoryMethod != null) {
|
||||
currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod);
|
||||
}
|
||||
else {
|
||||
currentlyInvokedFactoryMethod.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new BeanDefinitionStoreException(
|
||||
@@ -159,4 +171,12 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the factory method currently being invoked or {@code null} if none.
|
||||
* Allows factory method implementations to determine whether the current
|
||||
* caller is the container itself as opposed to user code.
|
||||
*/
|
||||
public static Method getCurrentlyInvokedFactoryMethod() {
|
||||
return currentlyInvokedFactoryMethod.get();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user