728x90
DI (Dependency Injection) - 의존성 주입
DI란?
객체가 필요로 하는 의존성을 스프링 컨테이너가 주입하는 설계 패턴입니다.
DI는 IoC의 구체적인 구현 방식 중 하나로, 객체 간의 의존성을 스프링 컨테이너가 자동으로 설정해줍니다.
DI의 핵심
- 객체 간의 의존성을 스프링 컨테이너(ApplicationContext)가 관리.
- 객체를 필요로 하는 클래스는 의존성을 직접 생성하지 않고 외부에서 주입받음
- 주입 방법: 생성자, 필드, Setter(메서드)를 통해 이루어짐
DI의 장점
- 결합도 감소
- 클래스 간의 강한 의존성을 없애고 유연성을 높임
- 객체가 내부의 의존성을 생성하지 않으므로, 다른 객체로 쉽게 교체 가능
- 테스트 용이성
- DI를 통해 실제 객체 대신 Mock 객체를 주입하여 유닛 테스트를 쉽게 작성 가능
- 재사용성 증가
- 객체가 외부 의존성에 대해 알 필요가 없으므로 코드 재사용성이 향상
DI의 주요 방법
스프링에서는 3가지 DI 방법을 제공합니다.
1. 생성자 주입(Constructor Injection) - 권장 방식
- 객체 생성 시점에 의존성을 주입받는 방식
- 의존성을 final로 선언할 수 있어, 불변성을 보장하고 안정적임
@Component
class MyService(val repository: MyRepository)
@Component
class MyRepository
- 장점
- 필수 의존성을 명확히 알 수 있음
- 객체가 생성될 때 모든 의존성을 주입받아야 하므로 의존성 누락 방지
2. 필드 주입(Field Injection)
- 클래스 내부의 필드에 의존성을 직접 주입하는 방식
- @Autowired를 사용하여 주입
@Component
class MyService {
@Autowired lateinit var repository: MyRepository
}
- 장점
- 구현이 간단하고 코드가 짧음
- 단점
- 테스트 시 Mock 객체를 주입하기 어려움
- 의존성이 숨겨져 코드 가독성이 떨어질 수 있음
3. Setter 주입(Setter Injection)
- Setter 메서드를 통해 의존성을 주입받는 방식
@Component
class MyService {
private lateinit var repository: MyRepository
@Autowired
fun setRepository(repository: MyRepository) {
this.repository = repository
}
}
- 장점
- 선택적인 의존성에 적합
- 주입된 의존성을 나중에 변경 가능
- 단점
- 의존성이 명시적이지 않아 필수 의존성과 선택적 의존성 구분하기 어려움
DI 예제
1. 의존성이 있는 클래스
@Component
class MyRepository {
fun findData(): String {
return "Hello from Repository"
}
}
2. DI를 사용하는 서비스 클래스
@Service
class MyService(private val repository: MyRepository) {
fun getData(): String {
return repository.findData()
}
}
3. DI를 사용하는 컨트롤러
@RestController
@RequestMapping("/api")
class MyController(private val service: MyService) {
@GetMapping("/data")
fun getData(): String {
return service.getData()
}
}
- 동작 원리
- 스프링 컨테이너(ApplicationContext)가 @Component, @Service, @RestController 등을 스캔해 Bean을 등록.
- MyService가 MyRepository를 필요로 하면, 컨테이너가 MyRepository를 생성하여 MyService에 주입.
- MyController가 MyService를 필요로 하면, 컨테이너가 주입.
DI의 동작 원리
- ApplicationContext가 Bean 스캔 및 등록
- @ComponentScan에 의해 모든 Bean을 스캔하여 컨테이너에 등록
- 등록된 Bean들은 스프링 컨테이너가 관리
- Bean 생성 및 주입
- 컨테이너가 의존성을 확인하고 필요한 Bean을 생성 후 주입
- 의존성 주입 완료 후 애플리케이션 실행
- 모든 의존성이 주입된 상태로 애플리케이션이 시작됨
DI의 한계 및 보완
- Bean 순환 참조 문제
- 서로 의존하는 Bean 간에 순환 참조 문제가 발생할 수 있음
- 해결책: 순환 참조를 제거하거나 @Lazy로 지연 주입
- Bean 스코프 관리
- 기본적으로 Bean은 싱글톤으로 관리되며, 이를 명시적으로 변경해야 하는 경우가 있음
- 해결책: @Scope로 Bean 스코프를 설정
요약
- DI란 객체가 필요로 하는 의존성을 외부에서 주입받아 결합도를 줄이고 유연성을 높이는 설계 방식
- 스프링에서 DI 구현 방법은 생성자 주입, 필드 주입, Setter 주입 3가지 방식이 있다.
- 장점: 결합도 감소, 테스트 용이성 증가, 재사용성 향상
- 동작 원리: 스프링 컨테이너가 Bean을 생성 및 관리하며, 필요한 객체를 자동으로 주입한다.
728x90
'Kotlin' 카테고리의 다른 글
Spring - Servlet(서블릿)이란? (0) | 2024.12.24 |
---|---|
AOP (Aspect-Oriented Programming) - 관점 지향 프로그래밍 (0) | 2024.12.23 |
스프링 IoC(Inversion of Contorl) - 제어의 역전 (1) | 2024.12.20 |
Kotlin Class 알아보기 1(Class, Data Class, Enum Class, Abstract Class) (1) | 2024.12.19 |