1. IoC (제어의 역전, Inversion of Control)
- 프로그램의 제어 흐름을 구현된 객체가 직접 관리하는 것이 아니라 외부에서 관리하는 것.
- 기존 프로그램에서는 클라이언트 구현 객체가 직접 서버 구현 객체를 생성/연결/실행했다면, Config 클래스에서 필요한 객체를 생성 후 클라이언트에 주입하는 방식으로 변경하는 것이다.
- 프로그램 제어 흐름에 대한 권한을 담당하는 외부 클래스가 따로 있는 것.
2. 프레임워크, 라이브러리
- 프레임워크 : 내가 작성한 코드를 제어하고, 대신 실행한다.
- 라이브러리 : 내가 작성한 코드가 직접 제어의 흐름을 담당한다.
3. DI (의존관계 주입, Dependency Injection)
- 의존성 : 객체를 생성 및 사용함에 있어 의존 관계가 있는 경우. “이 객체를 알고 있는 경우”
- 객체를 직접 생성하는 것이 아니라 외부에서 생성 후 주입시켜주는 방식을 말한다.
- DI를 통해 객체 간의 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮출 수 있다.
- 클라이언트 코드를 변경하지 않고도, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다.
DI 방법 3가지
1. 생성자 주입(Construct Injection)
- 현재 가장 권장되는 의존 관계 주입 방식.
- 하나의 생성자가 존재할 때 필드주입의 단점을 대부분 극복한다. 생성자를 1개만 두고 @Autowired를 생략하는 방법을 추천한다.
- Lombok 라이브러리의 @RequiredArgsConstructor를 함께 사용하면 생성자를 생략할 수 있어 코드가 간결해진다.
- final 키워드를 사용할 수 있으므로 객체의 불변성이 보장되고 Null Pointer Exception이 발생하지 않는다.
class Client {
private final Service service;
// 생성자 주입
public Client(Service service) {
this.service = service;
}
}
2. 필드 주입 (Field Injection)
- Bean으로 등록된 객체를 사용하고자 하는 클래스에 Field로 선언한 뒤 @Autowired를 붙여주면 끝이다.
- 간단하지만, 참조 관계를 한눈에 확인하기 어렵고 순환 참조를 막을 수 없다.
- 순환 참조 : A가 B를 가지고 있고, B가 A를 가지고 있으므로 실행 전까지 Error를 잡을 수 없다.
- 생성자 주입을 제외한 나머지 (필드 주입, setter 주입)은 모두 생성자 이후에 호출되므로, 필드에 final 키워드를 사용할 수 없다. 따라서 불변성이 보장되지 않고 변경될 수 있다.
- Solid원칙 중 단일 책임 원칙(SRP)을 위반한다.
class Client {
@Autowired // 필드 주입
private Service service;
}
3. Setter 주입 (Setter Injection)
- Spring에서 @Autowired 어노테이션을 사용하여 Setter메서드를 통해 주입
- Null Pointer Exception이 발생할 수 있다. (final 키워드 사용 불가)
// 의존성을 주입받는 클래스
class Client {
private Service service;
@Autowired // Setter 주입
public void setService(Service service) {
this.service = service;
}
}
4. IoC 컨테이너, DI 컨테이너
- Config 클래스와 같이 객체를 생성하고 주입하면서 의존 관계를 연결해주는 것.
- 최근에는 DI 컨테이너로 많이 불리며, 어셈블러, 오브젝트 팩토리로 불리기도 한다.
5. 스프링 컨테이너
AppConfig와 같이 객체 생성 주입을 담당하는 클래스를 바로 호출할수도 있지만,
스프링 컨테이너를 사용하면 AppConfig에서 만든 객체들을 한번에 관리할 수 있어 더욱 편리하다.
=> 스프링 컨테이너 관련 글 :