Backend Development 16 min read

Deep Dive into Spring Framework: IOC Container, BeanFactory, Bean Loading, and FactoryBean

This article provides an in‑depth exploration of Spring's core concepts—including the IOC container, ApplicationContext vs BeanFactory, bean instantiation lifecycles, and the role of FactoryBean—supplemented with diagrams and source code excerpts to help Java developers understand the framework's inner workings.

Java Captain
Java Captain
Java Captain
Deep Dive into Spring Framework: IOC Container, BeanFactory, Bean Loading, and FactoryBean

1. Spring IOC Container

The IoC (Inversion of Control) principle reverses the responsibility of obtaining dependent objects, allowing the container to manage object lifecycles and dependencies, which reduces coupling and improves testability. Spring achieves this through XML configuration and the IoC container.

The container loading process is illustrated by the second diagram.

BeanDefinition is an interface that represents the metadata of a <bean> element (class, scope, lazy‑init, etc.). All bean definitions are ultimately converted to a unified internal structure, enabling the BeanFactory to instantiate beans on demand.

2. ApplicationContext vs BeanFactory

When the container starts, AbstractApplicationContext's refresh() method is invoked. The commonly used implementation ClassPathXmlApplicationContext extends AbstractApplicationContext, as shown in the class diagram.

The core of the refresh logic is the following method (original formatting retained):

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 设置和校验系统变量和环境变量的值
        prepareRefresh();
        // 主要是创建 beanFactory,同时加载配置文件.xml 中的 beanDefinition
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        prepareBeanFactory(beanFactory);
        try {
            // 注册标准组件、后置处理器、事件多播器等
            postProcessBeanFactory(beanFactory);
            invokeBeanFactoryPostProcessors(beanFactory);
            registerBeanPostProcessors(beanFactory);
            initMessageSource();
            initApplicationEventMulticaster();
            onRefresh();
            registerListeners();
            // 实例化非延迟加载的单例 Bean
            finishBeanFactoryInitialization(beanFactory);
            finishRefresh();
        } catch (BeansException ex) {
            logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        }
    }
}

This method creates and loads the Spring container configuration, registers standard components, post‑processors, event listeners, and finally instantiates singleton beans.

The BeanFactory interface defines the basic operations for retrieving beans, checking their scope, and querying metadata. Its key methods are shown below:

package org.springframework.beans.factory;

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";
    Object getBean(String name) throws BeansException;
T getBean(String name, Class
requiredType) throws BeansException;
T getBean(Class
requiredType) throws BeansException;
    Object getBean(String name, Object... args) throws BeansException;
    boolean containsBean(String name);
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, Class
targetType) throws NoSuchBeanDefinitionException;
    Class
getType(String name) throws NoSuchBeanDefinitionException;
    String[] getAliases(String name);
}

Implementations such as DefaultListableBeanFactory, XmlBeanFactory, and various ApplicationContext classes provide the concrete behavior.

3. Bean Loading Process

Spring determines when a bean is instantiated based on its scope and lazy‑init setting:

BeanFactory (e.g., XmlBeanFactory): beans are created on first use.

ApplicationContext with singleton scope and lazy‑init=false: beans are instantiated during container startup and cached in a thread‑safe ConcurrentHashMap.

Singleton with lazy‑init=true or prototype beans: instantiated on first request.

The core loading logic resides in doGetBean , which handles name transformation, singleton cache lookup, dependency resolution, scope handling, and finally delegates to createBean for actual instantiation. A simplified excerpt is shown:

protected
T doGetBean(final String name, final Class
requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
    final String beanName = transformedBeanName(name);
    Object bean = null;
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    } else {
        // create new bean instance
        if (isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
        // resolve parent factories, check definitions, etc.
        RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> createBean(beanName, mbd, args));
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        } else if (mbd.isPrototype()) {
            Object prototypeInstance = null;
            try {
                beforePrototypeCreation(beanName);
                prototypeInstance = createBean(beanName, mbd, args);
            } finally {
                afterPrototypeCreation(beanName);
            }
            bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
        } else {
            // request, session, custom scopes
            Scope scope = this.scopes.get(mbd.getScope());
            if (scope == null) {
                throw new IllegalStateException("No Scope registered for scope '" + mbd.getScope() + "'");
            }
            Object scopedInstance = scope.get(beanName, () -> createBean(beanName, mbd, args));
            bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
        }
    }
    if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
        throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
    }
    return (T) bean;
}

The method performs name transformation, checks singleton caches, resolves parent factories, creates beans according to their scope, handles circular dependencies, and finally returns the appropriate bean instance.

4. FactoryBean

FactoryBean is a special interface that allows developers to customize the creation logic of a bean. While BeanFactory is the container that holds beans, a FactoryBean itself is a bean that produces other beans, similar to a factory pattern.

public interface FactoryBean
{
    // Return the object created by the FactoryBean; if isSingleton() is true, the object is cached as a singleton.
    T getObject() throws Exception;
    // Return the type of object that this FactoryBean creates.
    Class
getObjectType();
    // Indicate whether the created object is a singleton.
    boolean isSingleton();
}

Further reading on FactoryBean usage can be found at the referenced blog links.

Conclusion

Reading Spring source code offers several benefits: it teaches the framework's design philosophy and coding conventions, reveals recurring patterns such as the "do" prefix for core operations, and provides a deeper understanding that can be transferred to other frameworks. The author encourages continuous study of the source to become a more proficient developer.

Next article preview: "What is Spring AOP and how to use it?"

backendJavaIoCSpringDependency InjectionFactoryBeanBeanFactory
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.