Backend Development 38 min read

Deep Dive into Spring Boot: IoC Container, SpringFactoriesLoader, Event Publishing, and Auto‑Configuration

This extensive tutorial explains Spring Boot’s core mechanisms—including the IoC container, class loading, SpringFactoriesLoader, event publishing, and automatic configuration—through detailed explanations and code examples, helping developers understand and extend Spring Boot’s startup process.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Deep Dive into Spring Boot: IoC Container, SpringFactoriesLoader, Event Publishing, and Auto‑Configuration

The article begins with an introduction to Spring Boot, emphasizing that its most exciting feature is the rapid startup of Spring applications and that understanding the underlying Spring IoC container is essential for mastering Spring Boot.

Spring IoC Container Basics

The IoC container is compared to a restaurant: you request a bean and the container provides a fully‑initialized instance without you needing to know how it was created. Bean definitions are stored in BeanDefinition objects, which hold class type, constructor arguments, and other metadata. The relationship between BeanFactory , BeanDefinitionRegistry , and BeanDefinition is illustrated with a diagram (omitted).

Example code showing how a bean is registered and retrieved:

// Default container implementation
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
// Create BeanDefinition for Business class
AbstractBeanDefinition definition = new RootBeanDefinition(Business.class, true);
// Register the bean definition
beanRegistry.registerBeanDefinition("beanName", definition);
// Retrieve the bean instance
BeanFactory container = (BeanFactory) beanRegistry;
Business business = (Business) container.getBean("beanName");

Another example demonstrates loading bean definitions from an XML file using XmlBeanDefinitionReader :

BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory();
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl(beanRegistry);
beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");
BeanFactory container = (BeanFactory) beanRegistry;
Business business = (Business) container.getBean("beanName");

The container lifecycle is split into two phases: the container‑startup phase (loading configuration metadata) and the bean‑instantiation phase (creating bean instances on demand).

Class Loading and SPI

The article explains Java’s three class loaders (Bootstrap, Extension, Application) and the parent‑delegation model. It also discusses the limitation of the delegation model for Service Provider Interfaces (SPI) and introduces the thread‑context class loader as a solution.

Example of loading a resource using the thread‑context class loader:

String name = "java/sql/Array.class";
Enumeration
urls = Thread.currentThread().getContextClassLoader().getResources(name);
while (urls.hasMoreElements()) {
    URL url = urls.nextElement();
    System.out.println(url);
}

SpringFactoriesLoader

SpringFactoriesLoader scans all JARs for META-INF/spring.factories files, parses key‑value pairs, and returns the class names associated with a given key. The core method is:

public static List
loadFactoryNames(Class
factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    Enumeration
urls = (classLoader != null ?
        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    List
result = new ArrayList<>();
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        Properties props = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
        String names = props.getProperty(factoryClassName);
        result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(names)));
    }
    return result;
}

Typical content of a spring.factories file:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration

Auto‑Configuration Mechanism

The annotation @EnableAutoConfiguration imports EnableAutoConfigurationImportSelector , which uses SpringFactoriesLoader to obtain all auto‑configuration classes listed under the key EnableAutoConfiguration . Each auto‑configuration class is a @Configuration class guarded by conditional annotations such as @ConditionalOnClass , @ConditionalOnMissingBean , etc. Example:

@Configuration
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class})
public class DataSourceAutoConfiguration { }

These conditions ensure that the configuration is applied only when the required classes are present and when no user‑defined bean of the same type exists.

Spring Boot Startup Flow

The entry point is a class annotated with @SpringBootApplication , which combines @SpringBootConfiguration , @EnableAutoConfiguration , and @ComponentScan . The SpringApplication.run() method performs the following high‑level steps (simplified):

Create SpringApplicationRunListeners via SpringFactoriesLoader and invoke starting() (publishes ApplicationStartedEvent ).

Prepare the Environment (profiles, property sources) and publish ApplicationEnvironmentPreparedEvent .

Print the banner.

Create an appropriate ApplicationContext (web or non‑web).

Instantiate FailureAnalyzer implementations.

Prepare the context: set the environment, apply ApplicationContextInitializer instances, and publish ApplicationContextInitializedEvent .

Refresh the context, which among other things invokes all BeanFactoryPostProcessor s (including those added by the auto‑configuration import selector).

Call any CommandLineRunner or ApplicationRunner beans.

Publish the final ApplicationReadyEvent via finished() on the listeners.

During the refresh phase, ConfigurationClassPostProcessor processes annotations like @ComponentScan , @Import , and ultimately triggers the auto‑configuration logic described earlier.

Extending Spring Boot

Developers can add custom ApplicationListener implementations either programmatically via SpringApplication.addListeners() or declaratively by adding entries to their own META-INF/spring.factories file under the org.springframework.context.ApplicationListener key.

The article concludes with a reminder that mastering the Spring container’s lifecycle makes understanding Spring Boot’s startup process straightforward.

Spring BootAuto‑ConfigurationIoC ContainerEvent PublishingSpringFactoriesLoader
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

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.