Build a Mini Spring IoC Container in 5 Minutes: Hands‑On Tutorial
This article walks through the fundamentals of Spring’s IoC container by manually implementing a lightweight version, covering concepts such as IOC, DI, bean definitions, resource loading, bean registration, and a simple BeanFactory with example code and a test demonstrating singleton caching.
Spring is a widely used open‑source framework that has grown into a large ecosystem, making its core concepts hard to grasp at a glance.
This section returns to the essence of Spring and shows how to hand‑craft a minimal Spring container in five minutes.
What Is IOC?
Spring was introduced by Rod Johnson, whose books challenged the traditional Java EE EJB model and led to the creation of the lightweight Spring framework.
Spring’s two core kernels are IOC and AOP, with IOC being the most fundamental.
IOC (Inversion of Control) means the container manages object lifecycles and dependencies, delivering the required objects instead of the code creating them directly.
After introducing IOC, control over object lifecycles shifts from the objects themselves to the container, which is why the term "Inversion of Control" is used.
DI (Dependency Injection) is the concrete way the container supplies required dependencies when instantiating objects, complementing IOC.
Factory and Spring Container
Spring can be viewed as a large factory. To understand its inner workings, we examine a simplified “mini” Spring container.
Produce Products : In Spring, beans are instantiated by the container using reflection.
Inventory Products : Created beans are cached so they are not recreated on every request.
Order Processing : Bean definitions (XML or annotations) act as orders that describe what objects to create and how they depend on each other.
The following sections describe the implementation of this mini container.
Order: Bean Definition
Bean definitions are stored in a simple properties file where each
keyis a bean name and each
valueis the fully‑qualified class name.
<code>userDao:cn.fighter3.bean.UserDao</code>The corresponding Java class representing a bean definition:
<code>public class BeanDefinition {
private String beanName;
private Class beanClass;
// getters and setters omitted
}</code>Order Fulfillment: Resource Loader
The resource loader reads the properties file and builds a map of bean names to
BeanDefinitionobjects.
<code>public class ResourceLoader {
public static Map<String, BeanDefinition> getResource() {
Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(16);
Properties properties = new Properties();
try {
InputStream inputStream = ResourceLoader.class.getResourceAsStream("/beans.properties");
properties.load(inputStream);
for (String key : properties.stringPropertyNames()) {
String className = properties.getProperty(key);
BeanDefinition bd = new BeanDefinition();
bd.setBeanName(key);
bd.setBeanClass(Class.forName(className));
beanDefinitionMap.put(key, bd);
}
inputStream.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return beanDefinitionMap;
}
}</code>Order Allocation: Bean Register
BeanRegister acts as a singleton cache for beans.
<code>public class BeanRegister {
private Map<String, Object> singletonMap = new HashMap<>(32);
public Object getSingletonBean(String beanName) {
return singletonMap.get(beanName);
}
public void registerSingletonBean(String beanName, Object bean) {
if (singletonMap.containsKey(beanName)) return;
singletonMap.put(beanName, bean);
}
}</code>Production Floor: BeanFactory
BeanFactory ties everything together: it loads resources, creates the BeanRegister, and provides
getBeanto retrieve or create beans.
<code>public class BeanFactory {
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
private BeanRegister beanRegister;
public BeanFactory() {
beanRegister = new BeanRegister();
this.beanDefinitionMap = new ResourceLoader().getResource();
}
public Object getBean(String beanName) {
Object bean = beanRegister.getSingletonBean(beanName);
if (bean != null) return bean;
return createBean(beanDefinitionMap.get(beanName));
}
private Object createBean(BeanDefinition beanDefinition) {
try {
Object bean = beanDefinition.getBeanClass().newInstance();
beanRegister.registerSingletonBean(beanDefinition.getBeanName(), bean);
return bean;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}</code>Testing the Mini Container
Example bean class:
<code>public class UserDao {
public void queryUserInfo() {
System.out.println("A good man.");
}
}</code>JUnit test demonstrating bean retrieval and singleton caching:
<code>public class ApiTest {
@Test
public void test_BeanFactory() {
BeanFactory beanFactory = new BeanFactory();
UserDao userDao1 = (UserDao) beanFactory.getBean("userDao");
userDao1.queryUserInfo();
UserDao userDao2 = (UserDao) beanFactory.getBean("userDao");
userDao2.queryUserInfo();
}
}</code>Running the test prints:
<code>A good man.
A good man.</code>Thus a functional, though simplified, Spring‑style IoC container is built. Potential improvements include better abstraction, extensibility, and decoupling.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.