개발 일기

[Spring Boot] Spring 3대 특징(Spring 삼각형) 2 - 의존성 주입(DI)과 제어의 역전(IoC) 본문

Back-End/Spring

[Spring Boot] Spring 3대 특징(Spring 삼각형) 2 - 의존성 주입(DI)과 제어의 역전(IoC)

개발 일기장 주인 2024. 3. 13. 00:05

이전 게시글에서는 스프링 삼각형과 POJO에 대해서 알아봤고 POJO는 IoC/DI, AOP, PSA를 통해서 달성할 수 있다는 것을 파악한 후 POJO에 대해서 정리했었다. 이제 POJO가 뭔지 알았으니 이제 어떠한 특징들을 통해서 이 POJO를 달성할 수 있느지 그중 첫번째로 IoC/DI에 대해 공부해보고자 한다.

Spring Framework는 객체의 생성부터 소멸까지, 또한 그 사이의 여러 생명주기 단계들을 Spring이 관리하며 필요할 때마다 Spring 컨테이너에서 객체를 꺼내어 사용할 수 있는 구조라고 했었는데 이때, 이 객체를 스프링 빈(Spring Bean)이라고 한다.

(추후에 이 스프링 IoC컨테이너와 스프링 빈(객체)에 대해서 더 자세하게 다뤄 보겠다.)

 

5분 개발이라는 분의 영상을 보고 DI/IoC를 이해하는데 큰 도움이 됐다.

DI (Dependency Injection - 의존성 주입) 이란?

우선 의존성이라는 단어를 생각해보자. 하나의 코드가 다른 코드에 의존한다는 것이 어떤 의미일까?

해당 코드를 보면 A 클래스가 B 클래스를 사용하고 있다.

이를 보고 "A는 B에 의존하고 있다 혹은 의존성이 있다." 라고 표현할 수 있다.

 

그렇다면 Dependency Injection이란?

"의존성 주입,  의존성을 주입한다, 의존성이 있는 객체를 주입한다" 라고 볼 수 있다.

즉, 쉽게 말해서 A라는 클래스가 B라는 클래스를 사용(의존)하고 있을 때 B 클래스를 A 클래스에서 직접 새로 생성해서 사용하는 것이 아니라 외부에서 B 클래스의 인스턴스를 생성해서 A 클래스에 주입해준다는 것이다.

 

강한 결합

의존성 주입(Dependency Injection, DI)과는 반대되는 개념으로 객체 내부에서 다른 객체를 생성하는 것은 강한 결합도를 가지는 구조.

A 클래스 내부에서 B 라는 객체를 직접 생성하고 있다면, B 객체를 C 객체로 바꾸고 싶은 경우에 A 클래스도 수정해야 하는 방식이기 때문에 강한 결합이다.

 

느슨한 결합

객체를 주입 받는다는 것은 외부에서 생성된 객체를 인터페이스를 통해서 넘겨받는 것이다.

이렇게 하면 결합도를 낮출 수 있고, 런타임시에 의존관계가 결정되기 때문에 유연한 구조를 가진다.

SOLID 원칙에서 O 에 해당하는 Open Closed Principle 을 지키기 위해서 디자인 패턴 중 전략패턴을 사용하게 되는데, 생성자 주입을 사용하게 되면 전략패턴을 사용하게 된다.

(SOLID에 대해서도 따로 게시글을 작성할 예정)

 

 

IoC (Inversion of Control - 제어의 역전) 이란?

IoC, 즉 '제어의 역전'(Inversion of Control)은 프로그래밍에서 매우 중요한 원칙 중 하나로, 특히 스프링 프레임워크를 이해하는 데 있어 핵심 개념이다. 이 원칙은 이름에서 암시하듯, 프로그램의 제어 흐름을 전통적인 방식에서 벗어나 역전시키는 것을 말한다. 참고로 스프링 프레임워크 만의 개념은 아니다.

기존 프로그래밍에서는, 프로그램의 흐름을 개발자가 직접 제어한다. 객체(스프링 빈)의 생성부터 생명주기 관리, 의존성의 해결까지 모든 것을 개발자가 코드로 명시적으로 관리한다.

예를 들어, 어떤 클래스가 다른 클래스의 인스턴스를 필요로 할 때, 개발자는 직접 해당 인스턴스를 생성하고, 필요한 의존성을 주입하는 코드를 작성해야 합니다.

Inversion of Control

하지만, IoC 원칙을 적용한 스프링 프레임워크에서는 이러한 제어권이 프레임워크 측으로 넘어갑니다. 즉, 스프링 컨테이너가 애플리케이션의 객체 생성, 의존성 주입, 생명주기 관리 등을 책임지게 됩니다. 개발자는 더 이상 객체의 생성 방법이나 의존성 관리 방법을 직접 코드로 작성하지 않습니다. 대신, 스프링 프레임워크에 이러한 책임을 '위임'하고, 필요한 설정만을 제공합니다.


이런 방식으로, 제어권의 흐름이 기존의 '개발자가 모든 것을 직접 제어한다'는 방식에서 '프레임워크가 중심이 되어 제어한다'는 방식으로 역전됩니다.

 

위의 IoC 왼쪽 사진에서 매개체는 바로 스프링 IoC 컨테이너라고 할 수 있다. 

(아까 말했듯이 추후에 이 스프링 IoC컨테이너와 스프링 빈(객체)에 대해서 더 자세하게 다뤄 보겠다.)

 

의존성 주입 방법

크게 3가지 방법이 있다. 하나씩 알아보자

  1. 생성자를 통한 의존성 주입
  2. Setter(수정자)를 통한 의존관계 주입
  3. 필드를 이용한 의존관계 주입

(추후에 따로 게시글에서 따로 정리해봐야할것같다.)

의존성 주입의 장점

그래서 의존성 주입이 왜하는 건데?라는 의문이 들었다. 크게 3가지로 볼 수 있을 것 같다.

  1. 의존성 감소
    클래스가 다른 클래스의 인스턴스를 직접 생성하지 않고, 외부(예: IoC 컨테이너)로부터 필요한 의존성을 주입하는 방식을 사용하면, 클래스 간의 결합도(의존성)가 낮아진다.
    의존하고 있는 모듈이 변경된다고 해서 이를 사용하는 곳에서는 주입만 받기 때문에 따로 신경쓸 필요가 없기 때문이다.
    결과적으로, 한 부분의 변경이 다른 부분에 미치는 영향을 최소화하여 변화에 강하기때문에 시스템의 전체적인 유연성이 향상되고 유지보수성이 증가하다. 또한 재사용성이 증가하여 확장성을 향상시킨다.

  2. 코드양 감소
    전통적인 방식에서는 객체의 생성과 관리를 직접 해야 한다. 하지만, 의존성 주입을 사용하면 이러한 부담이 줄어든다.
    IoC 컨테이너가 객체의 생명주기를 관리해주기 때문에, 개발자는 객체 생성 코드를 직접 작성할 필요가 없다.
    이는 코드의 양을 감소시킬 뿐만 아니라, 코드의 가독성과 관리 용이성도 향상시킨다.
     
  3. 테스트 용이
    의존성 주입은 테스트를 용이하게 하다. 특히, 단위 테스트에서 그 장점이 두드러진다.
    외부에서 테스트에 필요한 객체를 생성하여 주입할 수 있기 때문에, 테스트하고자 하는 클래스를 격리시켜 테스트할 수 있다.
    예를 들어, 실제 데이터베이스 대신에 메모리 기반 데이터베이스를 사용하는 것과 같이, 테스트 환경에 적합한 구성요소를 쉽게 교체하여 사용할 수 있다. 이는 테스트의 신뢰성을 높이고, 개발 과정에서 발생할 수 있는 버그를 조기에 발견할 수 있게 도와준다.

공부를 하면 할수록 공부해야할 추가적으로 연결되는 개념들이 너무 많다. 하나씩 잘 정리해봐야할 것 같다.