Understand Spring Boot's magic by building it from scratch: a manual DI container using custom annotations and reflection, property binding, condition evaluation, and application context lifecycle — all without any Spring dependency.
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
@Retention(RetentionPolicy.RUNTIME) @interface Component { String value() default ""; }
@Retention(RetentionPolicy.RUNTIME) @interface Inject {}
@Component("dataSource")
class DataSource {
public String getConnection() { return "jdbc:sqlite:app.db"; }
}
@Component("userRepository")
class UserRepository {
@Inject DataSource dataSource;
public String findUser(int id) { return "User[" + id + "] via " + dataSource.getConnection(); }
}
@Component("userService")
class UserService {
@Inject UserRepository repo;
public String getUser(int id) { return "Service -> " + repo.findUser(id); }
}
class DIContainer {
Map<String, Object> beans = new HashMap<>();
Map<Class<?>, Object> typeMap = new HashMap<>();
void register(Class<?>... classes) throws Exception {
for (Class<?> cls : classes) {
Component ann = cls.getAnnotation(Component.class);
String name = ann.value().isEmpty() ? lowerFirst(cls.getSimpleName()) : ann.value();
Object bean = cls.getDeclaredConstructor().newInstance();
beans.put(name, bean);
typeMap.put(cls, bean);
}
}
void wire() throws Exception {
for (Object bean : beans.values()) {
for (Field f : bean.getClass().getDeclaredFields()) {
if (f.isAnnotationPresent(Inject.class)) {
f.setAccessible(true);
Object dep = typeMap.get(f.getType());
if (dep == null) throw new RuntimeException("No bean for: " + f.getType());
f.set(bean, dep);
}
}
}
}
@SuppressWarnings("unchecked")
<T> T get(String name) { return (T) beans.get(name); }
static String lowerFirst(String s) { return Character.toLowerCase(s.charAt(0)) + s.substring(1); }
}
public class Main {
public static void main(String[] args) throws Exception {
DIContainer container = new DIContainer();
container.register(DataSource.class, UserRepository.class, UserService.class);
container.wire();
System.out.println("Beans registered: " + container.beans.keySet());
UserService svc = container.get("userService");
System.out.println(svc.getUser(42));
System.out.println(svc.getUser(99));
System.out.println("Manual DI container: 3 beans wired via reflection");
}
}
javac /tmp/Main.java -d /tmp && java -cp /tmp Main
Beans registered: [userRepository, dataSource, userService]
Service -> User[42] via jdbc:sqlite:app.db
Service -> User[99] via jdbc:sqlite:app.db
Manual DI container: 3 beans wired via reflection