skip to content

How does the container treat a FactoryBean's product regarding caching and bean lifecycle (post-processing, DI)?

level: seniorimportance: should knowfreq 28%

answer

  1. Factory = full lifecycle; product = not
  2. singleton product cached in factoryBeanObjectCache
  3. product skips @Autowired + @PostConstruct
  4. postProcessAfterInitialization still runs -> AOP can wrap
  5. AbstractFactoryBean handles caching + destroyInstance

basics

~20 s

For a singleton FactoryBean the container calls getObject() once and caches the product. The product is NOT a fully lifecycle-managed bean: it skips @Autowired injection and init callbacks, though BeanPostProcessors' after-initialization phase is applied to it.

solid answer

~40 s

The FactoryBean itself is a normal bean with full lifecycle (DI, init/destroy callbacks, all post-processors). Its product is different. If isSingleton() is true, the container invokes getObject() once and caches the result in FactoryBeanRegistrySupport.factoryBeanObjectCache; if false, getObject() runs per lookup. Crucially, the product does not receive the standard bean lifecycle — no @Autowired/@Value injection, no @PostConstruct/InitializingBean callbacks — because you constructed it inside getObject(). However, AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean routes singleton products through applyBeanPostProcessorsAfterInitialization, so BeanPostProcessor.postProcessAfterInitialization DOES run on them (which is how AOP auto-proxying can wrap a FactoryBean product). Destruction callbacks generally aren't managed for the product either. So: wire dependencies yourself inside getObject(), and don't rely on container-managed init/destroy for the produced object.

code

java · 22 lines
java
import org.springframework.beans.factory.config.AbstractFactoryBean;

// AbstractFactoryBean handles singleton caching AND product destruction hooks.
public class PoolFactoryBean extends AbstractFactoryBean<ConnectionPool> {

    private final DbConfig config; // injected into the FACTORY (full lifecycle)
    public PoolFactoryBean(DbConfig config) { this.config = config; }

    @Override
    public Class<?> getObjectType() { return ConnectionPool.class; }

    @Override
    protected ConnectionPool createInstance() {
        // manual wiring — no @Autowired happens on the product
        return new ConnectionPool(config.url(), config.maxSize());
    }

    @Override
    protected void destroyInstance(ConnectionPool pool) {
        pool.shutdown(); // container calls this on context close for the singleton product
    }
}

go deeper

for a junior

May only know singletons are cached; deeper lifecycle nuance is above level.

for a middle

Should know the product skips DI/init callbacks and singletons are cached.

for a senior

Should cite postProcessObjectFromFactoryBean/after-init post-processing enabling AOP and factoryBeanObjectCache.

for a principal

Should reason about migration pitfalls from @Bean, destruction management via AbstractFactoryBean, and post-processor ordering subtleties.

## Two objects, two lifecycles A FactoryBean scenario always involves two objects: 1. **The factory bean** — instantiated and managed by the container with the *full* lifecycle: constructor/setter/field injection, `@PostConstruct`/`InitializingBean.afterPropertiesSet`, all `BeanPostProcessor` phases, and destruction callbacks. 2. **The product** — created by *your* `getObject()` code, so it does **not** automatically go through the container's creation pipeline. ## Caching of the product - `isSingleton() == true` (default): the container calls `getObject()` once and stores the result in `FactoryBeanRegistrySupport.factoryBeanObjectCache` (keyed by bean name). Every later resolution returns that cached instance. Your `getObject()` implementation need not cache — the container does. - `isSingleton() == false`: the container calls `getObject()` on every request, returning fresh instances. There is no product caching. ## What lifecycle the product gets — and doesn't Because you built the product yourself: - **No dependency injection** into the product: `@Autowired`, `@Value`, setter/constructor injection are *not* applied by the container. You must wire collaborators inside `getObject()` (often by having the factory itself be injected with them). - **No standard init callbacks**: `@PostConstruct`, `InitializingBean.afterPropertiesSet()`, custom `init-method` are *not* invoked on the product. - **BeanPostProcessor after-initialization DOES run**: `AbstractAutowireCapableBeanFactory.getObjectFromFactoryBean` → `postProcessObjectFromFactoryBean` → `applyBeanPostProcessorsAfterInitialization` is applied to a **singleton** product. This is why AOP/auto-proxy infrastructure (an `AbstractAutoProxyCreator`, a `BeanPostProcessor`) can still wrap a FactoryBean product in a proxy. Note only the *after-initialization* phase runs — not `postProcessBeforeInitialization` and not the instantiation-aware phases. - **Destruction**: the container does not, in general, manage `@PreDestroy`/`DisposableBean` for the product. If the product needs cleanup, the factory should implement `DisposableBean` and clean up its product, or the product must self-manage. ## Practical implications / gotchas - Don't expect `@Autowired` fields inside a product returned by `getObject()` to be populated — they won't be. Pass dependencies into the factory and hand them to the product manually. - Auto-proxying (transactions, security, caching aspects) *can* apply to the product via the after-init post-processor — but subtle ordering issues arise because the product isn't a first-class managed bean; test it. - For a non-singleton FactoryBean, post-processing behavior differs (the after-init post-processing described applies to the cached singleton path); expensive `getObject()` runs each call. - Prefer `AbstractFactoryBean<T>` as a base class: it handles singleton caching and offers `createInstance()`/`destroyInstance()` hooks, giving the product a rudimentary managed destruction path. ## When this bites you Migrating construction logic from an `@Bean` method (where the returned object *is* a full managed bean with DI and callbacks) into a FactoryBean silently drops `@PostConstruct` and field injection on the product — a classic source of NullPointerExceptions.

  • You moved a bean from an @Bean method to a FactoryBean and its @PostConstruct stopped firing. Why?
    Because the product is constructed inside getObject()/createInstance(), the container does not run the standard bean creation pipeline on it — @PostConstruct, InitializingBean, and @Autowired are not applied to the product. Only after-initialization BeanPostProcessing runs. You must invoke init logic yourself in getObject().
  • Can Spring AOP wrap the object returned by a FactoryBean?
    Yes — the singleton product passes through postProcessObjectFromFactoryBean, which applies applyBeanPostProcessorsAfterInitialization, so auto-proxy BeanPostProcessors can wrap it. But because the product isn't a first-class managed bean, verify ordering and that the aspect actually applies.
  • Where is the product cached and who caches it?
    The container caches it, not your code: singleton products live in FactoryBeanRegistrySupport.factoryBeanObjectCache keyed by bean name; getObject() is called only once.

saying these in an interview costs you the question

  • Assuming @Autowired/@PostConstruct work on the product
  • Thinking you must implement caching yourself for a singleton product
  • Claiming no post-processors ever touch the product (after-init phase does)
  • Expecting container-managed @PreDestroy on the product without AbstractFactoryBean/DisposableBean

context