
이번 정리는 Backend / OOP logic in Java 관점에서 DI가 왜 필요한지, 순수 Java → Spring Boot로 어떻게 확장되는지를 흐름 중심으로 정리한다.
A 클래스가 B 클래스를 내부에서 생성하거나 사용하면, A는 B에 의존한다고 말한다.
문제는 “누가 생성하느냐”이다.
public class Car {
private Owner owner;
public Car() {
owner = new Owner(); // 직접 생성
}
}
Car가 Owner를 직접 생성한다. 이 구조는 다음과 같은 문제를 가진다.
이걸 강한 결합(Tight Coupling)이라고 한다.
public class Car {
private Owner owner;
public Car(Owner owner) {
this.owner = owner;
}
}
이제 Car는 Owner를 직접 생성하지 않는다. 외부에서 전달받는다.
public class CarMain {
public static void main(String[] args) {
Owner owner = new Owner();
Car car = new Car(owner);
}
}
이 구조가 바로 의존성 주입이다.
Spring에서 가장 권장되는 방식이다.
public class Car {
private Owner owner;
public void setOwner(Owner owner) {
this.owner = owner;
}
}
Car car1 = new Car();
Car car2 = new Car();
Owner owner1 = new Owner();
car1.setOwner(owner1);
car2.setOwner(owner1);
하지만 필수 의존성이라면 생성자 주입이 더 안전하다.
public class CarController {
@Autowired
private CarDatabaseService carDatabaseService;
}
Spring이 리플렉션을 통해 필드에 직접 주입한다.
그래서 최근 실무에서는 거의 사용하지 않는다.
Spring은 ApplicationContext가 Bean을 관리한다.
@Component, @Service, @Repository, @Controller 이 애너테이션이 붙은 클래스는 Bean으로 등록된다.
@Repository
public class CarRepository {
public List<String> findAll() {
return List.of("BMW", "Benz");
}
}
@Service
public class CarService {
private final CarRepository carRepository;
public CarService(CarRepository carRepository) {
this.carRepository = carRepository;
}
public void printCars() {
System.out.println(carRepository.findAll());
}
}
Spring이 CarRepository를 생성하고, CarService 생성자에 자동 주입한다.
@SpringBootApplication은 다음을 포함한다.
ComponentScan이 현재 패키지 이하의 클래스를 스캔하여 Bean으로 등록한다.
CarRepository mockRepo = Mockito.mock(CarRepository.class);
CarService service = new CarService(mockRepo);
실제 DB 없이 테스트 가능.
인터페이스 기반 설계 가능.
public interface CarRepository {
List<String> findAll();
}
H2용, MariaDB용, Mock용 구현체 교체 가능.
Controller → Service → Repository → DB
각 계층은 직접 new 하지 않는다. Spring이 대신 생성하고 연결해준다.
이것이 IoC(Inversion of Control)이다. 객체 생성 권한이 개발자 → Spring으로 넘어간 것이다.
이제 중요한 질문 하나.
DI는 결국 “객체 생성 책임을 어디에 둘 것인가?”에 대한 설계 문제다.
Spring을 쓰는 이유는 단순히 편해서가 아니라, 이 생성 책임을 프레임워크에 위임하기 위해서다.
다음 단계는 Bean 생명주기(Lifecycle)와 Scope로 가면 DI가 완전히 이해된다.