Build a DI container
advanced20 min readLesson 147 of 180
Recursive constructor resolution, singleton memoization, and cycle detection in forty lines.
A DI container is three recursive rules:
- Resolve(type): find the constructor, resolve each parameter type, then instantiate.
- Memoize: one instance per type (singleton scope) โ otherwise every resolution builds a fresh object graph.
- Detect cycles: resolving A needs B needs A โ without an "in progress"
set, recursion never terminates (you get a
StackOverflowErrorin production, at 3 a.m.).
public <T> T resolve(Class<T> type) {
if (cache.containsKey(type)) return type.cast(cache.get(type));
if (!inProgress.add(type)) throw new IllegalStateException("circular dependency: " + type);
Constructor<?> ctor = type.getDeclaredConstructors()[0];
Object[] args = Arrays.stream(ctor.getParameterTypes())
.map(this::resolve).toArray();
T instance = type.cast(ctor.newInstance(args));
inProgress.remove(type);
cache.put(type, instance);
return instance;
}
Forty lines, zero dependencies โ and now Spring's @Autowired is a
consumer of ideas you own. Constructor injection keeps the graph honest:
dependencies are visible in the signature, final by default, and impossible
to construct half-baked.