제어의 역전 ( IoC , Inversion of Control ) : 프로그램의 제어 흐름이 뒤바뀜을 말한다.
일반적인 경우 사용자가 자신이 필요한 객체를 생성해서 사용한다.
Spring의 Controller , Service , Repository로 예시를 들어 객체 생성을 통한 "강한 결합"을 이해해보자
1. Contoller1 이 Service1 객체를 생성하여 사용할 때
public class Controller1 {
private final Service1 service1;
public Controller1() {
this.service1 = new Service1();
}
}
2. Service1 이 Repostiroy1 객체를 생성하여 사용할 때
public class Service1 {
private final Repository1 repository1;
public Service1() {
this.repository1 = new Repository1();
}
}
3. Repostiroy1 객체 선언
public class Repository1 { ... }
위와 같이 코드를 작성했을 때, 만약 다음과 같이 변경된다면 어떻게 될까?
- Repository1 객체 생성 시 DB 접속 id, pw 를 받아서 DB 접속 시 사용.
- 생성자에 DB 접속 id, pw 를 추가
[ 변경된 Repository1 ]
public class Repository1 {
public Repository1(String id, String pw) {
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:h2:mem:springcoredb", id, pw);
}
}
이 때 "강한 결합"의 문제점이 발생한다.
만약 Controller 5개가 각각 Service1을 생성하여 사용하고 있고, 이 상태에서 Repository1 생성자가 변경된다면 모든 Controller와 모든 Service의 코드도 변경되어야 한다.

만약 Controller와 Service의 개수가 매우 많다면 각각의 코드를 변경하는 작업은 엄청나게 힘들어지게 된다.
그렇다면 위와같은 강한 결합을 해결할 방법은 없을까?
다음과 같은 상황이 된다면?
- repository1 객체 생성을 딱 한번만 한다.
- 생성된 repository1 객체를 모든 곳에서 재사용한다.
1. Repository1 클래스 선언 및 객체 생성 → repository1
public class Repository1 { ... }
// 객체 생성
Repository1 repository1 = new Repository1();

2.Service1 클래스 선언 및 객체 생성 (repostiroy1 사용) → service1
Class Service1 {
private final Repository1 repitory1;
// repository1 객체 사용
public Service1(Repository1 repository1) {
this.repository1 = repository1;
}
}
// 객체 생성
Service1 service1 = new Service1(repository1);

3. Contoller1 선언 ( service1 사용)
Class Controller1 {
private final Service1 service1;
// service1 객체 사용
public Controller1(Service1 service1) {
this.service1 = service1;
}
}
4. 이 때 마찬가지로 Repository1 객체 생성시 id, pw 를 받아오게끔 수정된다면
public class Repository1 {
public Repository1(String id, String pw) {
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:h2:mem:springcoredb", id, pw);
}
}
>> Repository1 생성자가 변경되어도 나머지 Controller와 Service 코드들을 변경할 필요가 없게된다!
마찬가지로 Service1 생성자가 변경되어도 나머지 Controller 코드들을 변경할 필요가 없게된다.
이는 강한 결합이 느슨한 결합으로 변경된 것이다.
아래 사진을 보면 객체의 흐름이 뒤바뀐 것을 알 수 있다.

이번에는 좀 더 자세히 이해해보기 위해 프레임워크를 적용하지 않은, 우리가 그동안 작성해왔던 일반적인 프로그램을 생각해보자. 객체의 생명주기(객체의 생성, 초기화, 소멸, 메서드 호출 등)를 클라이언트 구현 객체가 직접 관리한다. 또한 다른 사람이 작성한 외부 코드(라이브러리)를 호출하더라도 해당 코드의 호출 시점 역시 직접 관리한다. 하지만 이러한 생명주기를 직접 관리하지 않는 경우라면 어떨까?
Spring과 같은 프레임워크를 사용할 때
Controller, Service 같은 객체들의 동작을 우리가 직접 구현하기는 하지만, 해당 객체들이 어느 시점에 호출될 지는 신경쓰지 않는다. 단지 프레임워크가 요구하는대로 객체를 생성하면, 프레임워크가 해당 객체들을 가져다가 생성하고, 메서드를 호출하고, 소멸시킨다. 프로그램의 제어권이 역전된 것이다.
[ IoC라는 개념을 도입함으로써 얻을 수 있는 것 ]
- 프로그램의 진행 흐름과 구체적인 구현을 분리시킬 수 있다.
- 구현체 사이의 변경이 용이하다.
- 객체 간 의존성이 낮아진다.
의존성 주입( DI, Dependency Injection )
흔히들 IoC와 DI를 동일시하고는 한다. 하지만 사실 IoC와 DI는 다른 개념이다. 정확히는 DI는 IoC 의 종류 중 하나이다. DI 없이도 IoC를 만족하는 프로그램을 만들 수 있다. IoC는 프로그램 제어권을 역전시키는 개념이고, DI는 해당 개념을 구현하기 위해 사용하는 디자인 패턴 중 하나로, 이름 그대로 객체의 의존관계를 외부에서 주입시키는 패턴을 말한다.
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
위와 같은 A라는 클래스가 있다고 하자. 이 클래스는 B라는 클래스를 필드로 가진다. B에 변경이 일어나면, A에도 영향을 미치게 된다. 이런 경우를 "A가 B에 의존한다" 라고 한다.
그렇다면 의존성 주입이란? 자연스럽게 이 의존성을 "외부"에서 주입해준다는 의미가 된다.
코드를 보면 의존 대상 B를 직접 생성(결정)하는 것이 아니라 외부로부터 주입받는다. 의존성을 외부로부터 주입받는다고 할 수 있다.
[ DI를 사용할 경우 장점. ]
- 의존성이 줄어든다. (변경에 덜 취약해진다.)
- 모의 객체를 주입할 수 있기 때문에 단위 테스트가 쉬워진다.
- 가독성이 높아진다.
- 재사용성이 높아진다.
다음 포스팅에서는 Spring IoC 컨테이너와 빈( Bean ) 에 대해서 알아본다.
[ 참고 ] : https://velog.io/@ohzzi/Spring-DIIoC-IoC-DI-%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0
'Spring' 카테고리의 다른 글
| CORS 정리 (0) | 2022.08.14 |
|---|---|
| [ Spring ] spring ioc 컨테이너와 Bean (0) | 2022.08.01 |