Spring Bean Lifecycle: Understanding FactoryBean Resolution and Smart Initialization
FactoryBean Handling in Spring's Bean Instantiation
Spring's getBean() method includes dedicated logic for FactoryBean instances. A FactoryBean is a special bean that produces an object via its getObject() method. When you call getBean() for a FactoryBean, Spring obtains the factory bean itself first and then invokes getObject() to retrieve the actual managed instance. If you need the raw FactoryBean rather than its product, prefix the bean name with &, for example &myFactoryBean.
After Spring parses all eligible bean definitions into merged bean definitions, the preInstantiateSingletons() method drives singleton creation. It determines whether a bean is a FactoryBean and triggers the appropriate instantiation path.
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
for (String beanName : beanNames) {
RootBeanDefinition mergedDef = getMergedLocalBeanDefinition(beanName);
if (!mergedDef.isAbstract() && mergedDef.isSingleton() && !mergedDef.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object factoryInstance = getBean(FACTORY_BEAN_PREFIX + beanName);
if (factoryInstance instanceof FactoryBean) {
FactoryBean<?> factory = (FactoryBean<?>) factoryInstance;
boolean eager;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
eager = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
} else {
eager = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (eager) {
getBean(beanName);
}
}
} else {
getBean(beanName);
}
}
}
for (String beanName : beanNames) {
Object singleton = getSingleton(beanName);
if (singleton instanceof SmartInitializingSingleton) {
StartupStep step = this.getApplicationStartup().start("spring.beans.smart-initialize")
.tag("beanName", beanName);
SmartInitializingSingleton smartBean = (SmartInitializingSingleton) singleton;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartBean.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
} else {
smartBean.afterSingletonsInstantiated();
}
step.end();
}
}
}
The logic unfolds in three stages:
- For each bean name, the merged
RootBeanDefinitionis fetched. - If the definition is concrete, singleton, non-lazy, a check with
isFactoryBeandecides whether to go through theFactoryBeanpath or the standard singleton path. - After all non-lazy singletons are created, any bean implementing
SmartInitializingSingletonhas itsafterSingletonsInstantiated()callback invoked automatically.
How isFactoryBean Determines the Bean Type
The isFactoryBean method is central to this decision. It first looks in the singleton cache, then falls back to the bean definition or delegates to a parent factory.
public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
String beanName = transformedBeanName(name);
Object beanInstance = getSingleton(beanName, false);
if (beanInstance != null) {
return (beanInstance instanceof FactoryBean);
}
if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {
return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);
}
return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
}
Key steps:
transformedBeanNamestrips the&prefix, returning the plain bean name for singleton lookup.getSingletonchecks the singleton pool. If an instance exists and is aFactoryBean, it returnstrueimmediately.- If the current factory lacks a definition but has a parent that is a
ConfigurableBeanFactory, the check is delegated to the parent. This supports hierarchical contexts like:
AnnotationConfigApplicationContext parentCtx = new AnnotationConfigApplicationContext();
parentCtx.register(AppConfig.class);
parentCtx.refresh();
AnnotationConfigApplicationContext childCtx = new AnnotationConfigApplicationContext();
childCtx.setParent(parentCtx);
childCtx.register(AppConfig1.class);
childCtx.refresh();
UserService service = childCtx.getBean(UserService.class);
service.test();
- If no isntance exists, the merged bean definition is examined:
protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) {
Boolean result = mbd.isFactoryBean;
if (result == null) {
Class<?> beanType = predictBeanType(beanName, mbd, FactoryBean.class);
result = (beanType != null && FactoryBean.class.isAssignableFrom(beanType));
mbd.isFactoryBean = result;
}
return result;
}
Spring predicts the bean type from the definition's beanClass property and checks if it implements FactoryBean. The outcome is cached in the root definition for subsequent calls.
Eager Initialization with SmartFactoryBean
After confirming a bean is a FactoryBean, Spring determines whether to eagerly invoke getObject() by inspecting the SmartFactoryBean extension. SmartFactoryBean adds an isEagerInit() method. If it returns true, the actual object is created during container startup rather than lazily on first access.
@Component
public class UserFactory implements SmartFactoryBean {
@Override
public Object getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
@Override
public boolean isEagerInit() {
return true;
}
}
When eager initialization is requested, preInstantiateSingletons calls getBean(beanName) immediately after obtaining the factory bean, causing the getObject() result to be stored in the singleton cache. Otherwise, the factory bean itself is stored, and getObject() is deferred until the bean is actually retrieved.