Spring IoC Container: Bean Caching, Creation, and Dependency Resolution

Core Container Interfaces

The foundation of the Spring Inversion of Control (IoC) container begins with the BeanProvider interface, which defines the most fundamental contract for retrieving bean instances.

public interface BeanProvider {
    Object retrieveBean(String identifier) throws BeanCreationException;

    <T> T retrieveBean(String identifier, Class<T> requiredType) throws BeanCreationException;

    <T> T retrieveBean(Class<T> requiredType) throws BeanCreationException;

    boolean containsInstance(String identifier);
}

To manage singleton scoped beans, the framework utilizes a separate contract called SingletonCache, which handles the storage and retrieval of fully initialized singleton objects.

public interface SingletonCache {
    Object fetchSingleton(String identifier);

    void enrollSingleton(String identifier, Object singletonInstance);
}

Three-Tier Caching Mechanism

The BaseSingletonCache implements the SingletonCache interface and introduces the critical three-tier caching mechanism. This design is essential for resolving circular dependencies during the bean creation lifecycle.

public class BaseSingletonCache implements SingletonCache {
    private final Map<String, Object> matureSingletons = new HashMap<>();
    private final Map<String, Object> prematureSingletons = new HashMap<>();
    private final Map<String, InstanceSupplier<?>> supplierCache = new HashMap<>();
    private final Map<String, Destroyable> destroyableBeans = new HashMap<>();

    @Override
    public Object fetchSingleton(String identifier) {
        Object instance = matureSingletons.get(identifier);
        if (instance == null) {
            instance = prematureSingletons.get(identifier);
            if (instance == null) {
                InstanceSupplier<?> supplier = supplierCache.get(identifier);
                if (supplier != null) {
                    instance = supplier.obtain();
                    prematureSingletons.put(identifier, instance);
                    supplierCache.remove(identifier);
                }
            }
        }
        return instance;
    }

    @Override
    public void enrollSingleton(String identifier, Object singletonInstance) {
        matureSingletons.put(identifier, singletonInstance);
        prematureSingletons.remove(identifier);
        supplierCache.remove(identifier);
    }

    protected void registerInstanceSupplier(String identifier, InstanceSupplier<?> supplier) {
        supplierCache.put(identifier, supplier);
    }

    public void trackDestroyableBean(String identifier, Destroyable bean) {
        destroyableBeans.put(identifier, bean);
    }

    public void clearSingletons() {
        List<String> identifiers = new ArrayList<>(destroyableBeans.keySet());
        for (String id : identifiers) {
            Destroyable destroyable = destroyableBeans.remove(id);
            try {
                destroyable.tearDown();
            } catch (Exception ex) {
                throw new BeanDestructionException("Tear down method on bean [" + id + "] failed", ex);
            }
        }
    }
}

Abstract Bean Retrieval and FactoryBean Handling

The AbstractBeanProvider extends the BaseSingletonCache. Its primary responsibility is to orchestrate the bean retrieval process: first checking the three-tier cache, and if not found, acquiring the ComponentDefinition to create a new instance. It also includes logic to handle CustomFactoryBean instances, caching their produced objects appropriately.

public abstract class AbstractBeanProvider extends BaseSingletonCache implements ConfigurableContext {
    private final Map<String, Object> factoryProductCache = new HashMap<>();
    private TypeConverter typeConverter;

    public Object retrieveBean(String identifier) throws BeanCreationException {
        Object cachedInstance = fetchSingleton(identifier);
        if (cachedInstance != null) {
            return resolveActualObject(cachedInstance, identifier);
        }

        ComponentDefinition definition = resolveDefinition(identifier);
        Object createdInstance = constructBean(identifier, definition);
        return resolveActualObject(createdInstance, identifier);
    }

    private Object resolveActualObject(Object instance, String identifier) {
        Object target = instance;
        if (instance instanceof CustomFactoryBean) {
            CustomFactoryBean<?> factory = (CustomFactoryBean<?>) instance;
            try {
                if (factoryisObjectSingleton()) {
                    target = factoryProductCache.get(identifier);
                    if (target == null) {
                        target = factory.produceObject();
                        factoryProductCache.put(identifier, target);
                    }
                } else {
                    target = factory.produceObject();
                }
            } catch (Exception ex) {
                throw new BeanCreationException("CustomFactoryBean failed to produce object for [" + identifier + "]", ex);
            }
        }
        return target;
    }

    protected abstract ComponentDefinition resolveDefinition(String identifier) throws BeanCreationException;
    protected abstract Object constructBean(String identifier, ComponentDefinition definition) throws BeanCreationException;

    @Override
    public TypeConverter getTypeConverter() { return typeConverter; }
    @Override
    public void setTypeConverter(TypeConverter typeConverter) { this.typeConverter = typeConverter; }
}

Bean Instantiation and Property Population

The AbstractWiringProvider extends AbstractBeanProvider to handle the actual creation logic, dependency injection, and early reference exposure for circular dependency resolution. During creation, it instantiates the bean, exposes an early singleton supplier to the cache, and then applies property values.

public abstract class AbstractWiringProvider extends AbstractBeanProvider {
    private Instantiator instantiator = new ReflectiveInstantiator();

    @Override
    protected Object constructBean(String identifier, ComponentDefinition definition) {
        return initializeBean(identifier, definition);
    }

    protected Object initializeBean(String identifier, ComponentDefinition definition) {
        Object beanInstance;
        try {
            beanInstance = instantiator.instantiate(definition);

            if (definition.isSingleton()) {
                Object finalInstance = beanInstance;
                registerInstanceSupplier(identifier, () -> acquireEarlyProxyReference(identifier, definition, finalInstance));
            }

            injectDependencies(identifier, beanInstance, definition);
        } catch (Exception ex) {
            throw new BeanCreationException("Bean instantiation failed for [" + identifier + "]", ex);
        }
        return beanInstance;
    }

    protected void injectDependencies(String identifier, Object beanInstance, ComponentDefinition definition) {
        try {
            for (AttributeHolder attribute : definition.getAttributeHolders()) {
                String fieldName = attribute.getKey();
                Object value = attribute.getValue();

                if (value instanceof DependencyReference) {
                    DependencyReference ref = (DependencyReference) value;
                    value = retrieveBean(ref.getTargetBeanName());
                } else {
                    Class<?> rawType = value.getClass();
                    Class<?> expectedType = resolveFieldType(beanInstance.getClass(), fieldName);
                    TypeConverter converter = getTypeConverter();
                    if (converter != null && converter.canTransform(rawType, expectedType)) {
                        value = converter.transform(value, expectedType);
                    }
                    Field targetField = beanInstance.getClass().getDeclaredField(fieldName);
                    targetField.setAccessible(true);
                    targetField.set(beanInstance, value);
                }
            }
        } catch (Exception ex) {
            throw new BeanCreationException("Dependency injection failed for [" + identifier + "]", ex);
        }
    }

    protected Object acquireEarlyProxyReference(String identifier, ComponentDefinition definition, Object beanInstance) {
        return beanInstance;
    }

    private Class<?> resolveFieldType(Class<?> clazz, String fieldName) throws NoSuchFieldException {
        return clazz.getDeclaredField(fieldName).getType();
    }
}

Tags: spring-ioc circular-dependencies singleton-cache bean-lifecycle dependency-injection

Posted on Tue, 29 Sep 2026 16:46:48 +0000 by killian_walsh@hotmail.com