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:
Chris Beams
2011-05-12 12:28:13 +00:00
parent 57206db152
commit 2afeb08e3c
5 changed files with 195 additions and 21 deletions

View File

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

View File

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

View File

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