Spring Boot (의존성 주입(Dependency Injection, DI))

최병현·2026년 2월 13일

spring boot

목록 보기
2/34

이번 정리는 Backend / OOP logic in Java 관점에서 DI가 왜 필요한지, 순수 Java → Spring Boot로 어떻게 확장되는지를 흐름 중심으로 정리한다.


1. 의존성이란 무엇인가

A 클래스가 B 클래스를 내부에서 생성하거나 사용하면, A는 B에 의존한다고 말한다.

문제는 “누가 생성하느냐”이다.


2. 의존성 주입이 없는 코드 (강한 결합)

public class Car {

    private Owner owner;

    public Car() {
        owner = new Owner();  // 직접 생성
    }
}

Car가 Owner를 직접 생성한다. 이 구조는 다음과 같은 문제를 가진다.

  • Owner 구현이 바뀌면 Car도 수정해야 함
  • 테스트 시 Mock 객체 사용이 어려움
  • 확장성이 떨어짐

이걸 강한 결합(Tight Coupling)이라고 한다.


3. 생성자 주입 (Constructor Injection)

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);
    }
}

이 구조가 바로 의존성 주입이다.

장점

  • 결합도 감소
  • 테스트 용이
  • 불변성 유지 가능 (final 필드 사용)

Spring에서 가장 권장되는 방식이다.


4. 세터 주입 (Setter Injection)

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);

특징

  • 객체 생성 후 의존성 주입
  • 선택적 의존성에 적합
  • 런타임 중 변경 가능

하지만 필수 의존성이라면 생성자 주입이 더 안전하다.


5. 필드 주입 (Field Injection)

public class CarController {

    @Autowired
    private CarDatabaseService carDatabaseService;
}

Spring이 리플렉션을 통해 필드에 직접 주입한다.

문제점

  • 테스트 어려움
  • 순수 Java 객체 생성 불가
  • 숨겨진 의존성

그래서 최근 실무에서는 거의 사용하지 않는다.


6. Spring Boot에서의 DI 구조

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 생성자에 자동 주입한다.


7. @SpringBootApplication 내부 구조

@SpringBootApplication은 다음을 포함한다.

  • @EnableAutoConfiguration
  • @ComponentScan
  • @Configuration

ComponentScan이 현재 패키지 이하의 클래스를 스캔하여 Bean으로 등록한다.


8. DI가 중요한 이유 (실무 관점)

1) 테스트 가능성

CarRepository mockRepo = Mockito.mock(CarRepository.class);
CarService service = new CarService(mockRepo);

실제 DB 없이 테스트 가능.

2) 확장성

인터페이스 기반 설계 가능.

public interface CarRepository {
    List<String> findAll();
}

H2용, MariaDB용, Mock용 구현체 교체 가능.


9. DI 흐름을 전체 구조로 보면

Controller → Service → Repository → DB

각 계층은 직접 new 하지 않는다. Spring이 대신 생성하고 연결해준다.

이것이 IoC(Inversion of Control)이다. 객체 생성 권한이 개발자 → Spring으로 넘어간 것이다.


10. 핵심 정리

  • 의존성 주입은 결합도를 낮춘다.
  • 생성자 주입이 가장 권장된다.
  • Spring은 ApplicationContext가 Bean을 관리한다.
  • @Autowired 없이도 생성자가 하나면 자동 주입된다.
  • DI는 테스트, 유지보수, 확장성을 위한 핵심 설계 원칙이다.

이제 중요한 질문 하나.

DI는 결국 “객체 생성 책임을 어디에 둘 것인가?”에 대한 설계 문제다.

Spring을 쓰는 이유는 단순히 편해서가 아니라, 이 생성 책임을 프레임워크에 위임하기 위해서다.

다음 단계는 Bean 생명주기(Lifecycle)와 Scope로 가면 DI가 완전히 이해된다.

profile
Develop

0개의 댓글