Careeroza
A Tiny DI Container in Plain Java
How Does Spring Do It?
Spring's real container is large, but the core idea fits in a page: scan for annotated classes, read each constructor with reflection, create the dependencies first (recursively), then create the bean and keep it as a singleton.
- Find classes marked with
@Component. - For each, look at the constructor parameter types.
- Get (or create) a bean for each parameter type. For an interface, find the component that implements it.
- Call the constructor with those beans and store the result.
MiniSpring
Example
A working DI container in about 50 lines, using our own @Component annotation and reflection. Note the creation order: dependencies are built before the beans that need them.
import java.lang.annotation.*;
import java.lang.reflect.Constructor;
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
MiniContainer ctx = new MiniContainer(List.of(
OrderService.class, ProductRepository.class, EmailNotifier.class, NotAComponent.class));
OrderService orders = ctx.getBean(OrderService.class);
orders.placeOrder(1L, 2);
System.out.println("Same OrderService instance? " + (orders == ctx.getBean(OrderService.class)));
System.out.println("Beans in container: " + ctx.beanNames());
}
}
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
@interface Component {}
class MiniContainer {
private final Map<Class<?>, Object> singletons = new LinkedHashMap<>();
private final List<Class<?>> components;
MiniContainer(List<Class<?>> scanned) throws Exception {
components = scanned.stream().filter(c -> c.isAnnotationPresent(Component.class)).toList();
for (Class<?> c : components) getBean(c); // eager creation, like Spring singletons
}
<T> T getBean(Class<T> type) throws Exception {
Class<?> impl = components.stream().filter(type::isAssignableFrom).findFirst()
.orElseThrow(() -> new IllegalStateException("No bean of type " + type.getSimpleName()));
if (singletons.containsKey(impl)) return type.cast(singletons.get(impl));
Constructor<?> ctor = impl.getDeclaredConstructors()[0];
Object[] deps = new Object[ctor.getParameterCount()];
for (int i = 0; i < deps.length; i++) deps[i] = getBean(ctor.getParameterTypes()[i]); // recursion
Object bean = ctor.newInstance(deps);
singletons.put(impl, bean);
System.out.println("Created bean: " + impl.getSimpleName());
return type.cast(bean);
}
List<String> beanNames() { return singletons.keySet().stream().map(Class::getSimpleName).toList(); }
}
interface Notifier { void send(String msg); }
@Component class EmailNotifier implements Notifier {
public void send(String msg) { System.out.println("EMAIL: " + msg); }
}
@Component class ProductRepository {
String findName(long id) { return id == 1L ? "Boat Rockerz 450" : "Unknown"; }
}
@Component class OrderService {
private final ProductRepository repo;
private final Notifier notifier;
OrderService(ProductRepository repo, Notifier notifier) { this.repo = repo; this.notifier = notifier; }
void placeOrder(long productId, int qty) { notifier.send("Order placed: " + qty + " x " + repo.findName(productId)); }
}
class NotAComponent {} // no annotation: ignored by the scanOutput
Created bean: ProductRepository Created bean: EmailNotifier Created bean: OrderService EMAIL: Order placed: 2 x Boat Rockerz 450 Same OrderService instance? true Beans in container: [ProductRepository, EmailNotifier, OrderService]
What Real Spring Adds on Top
- Classpath scanning (finds classes in packages without a list).
- Circular dependency detection with a clear error message.
@Qualifier,@Primary, scopes, lifecycle callbacks,@Valueinjection.- Proxies around beans so that
@Transactional,@Cacheable,@Asyncand security work without changing your code.
Exercise: Make the container fail clearly when a class needs a dependency that is not a component. What happens if you remove EmailNotifier from the list?
Show answer
// getBean(Notifier.class) finds no component implementing Notifier, so it throws:
// java.lang.IllegalStateException: No bean of type Notifier
// Spring gives the same kind of error at startup:
// "Parameter 1 of constructor in OrderService required a bean of type 'Notifier' that could not be found."