본문 바로가기
Group Study (22-23)/Spring-Core-JPA

[Spring-Core-JPA] 1주차 - DI, IoC 그리고 DI Container

by Razelo 2023. 2. 4.

작성자: 최병윤

 

백엔드 스터디 1주차 내용을 정리하고자 합니다.

 

1주차에 다루고자 하는 내용은 DI, IOC 그리고 DI Container 입니다. 


DI 란?

Dependency Injection은 의존성 주입이라고 부릅니다. 쉽게 말해 외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴입니다. 인터페이스를 사이에 둬서 클래스 레벨에서 의존관계가 고정되지 않도록 하고 런타임 시에 관계를 동적으로 주입해서 유연성을 확보하면서도 결합도를 낮출 수 있는 패턴입니다. 

 

여기서 의존성(Dependency)은 두 클래스 사이의 관계로 설명할 수 있습니다. A 클래스가 B클래스를 A클래스 내부의 변수로 사용한다고 가정할때 A클래스는 B클래스와 의존관계가 형성되었다고 말합니다. 그리고 이런 상황에서 B클래스를 수정하면 A클래스도 수정해야합니다. 즉 결합도가 높은 상태라고 말할 수 있습니다. 그렇기 때문에 결합도를 낯추기 위해서 DI를 사용하는 것이 좋습니다. 물론 몇 가지 이유가 더 있습니다. 

 

DI를 사용해야 하는 이유 

  • Unit Test가 용이해진다. 
  • 코드의 재활용성을 높여준다.
  • 객체 간의 의존성(종속성)을 줄이거나 없앨 수 있다. 
  • 객체 간의 결합도를 낮추면서 유연한 코드를 작성할 수 있다. 

주입(Injection)은 외부에서 객체를 생성해서 넣어주는 것을 의미하기 때문에 의존성 주입은 외부에 의해 의존관계가 완성된다고 볼 수 있습니다. 필요한 객체를 직접 생성하는 것이 아니라 외부로부터 필요한 객체를 받아서 사용하기 때문에 위 같은 장점이 있는 것입니다. 

 

DI는 추가적으로 의존성 분리라는 개념이 존재합니다. 왜냐면 DI는 의존성을 분리해서 사용하기 때문입니다. 그리고 의존성 분리는 흔히 SOLID 원칙에서 많이 들어본 의존관계 역전 원칙(Dependency Inversion Principle)에 의거해서 분리합니다.  

 

의존관계 역전 원칙이란 소프트웨어 모듈을 분리하는 특정 형식을 지칭합니다. 이 원칙에 의하면 상위 계층이 하위 계층에 의존하는 전통적인 의존관계를 반전 즉 역전시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있습니다. 

 

의존성 주입에는 네 가지 방식이 있습니다. 

 

1. 생성자 주입

생성자 호출 시점에 1회 호출 보장, 주입받은 객체가 변하지 않거나 반드시 객체의 주입이 필요한 경우에 강제하려 사용

2. 필드 주입

필드에 바로 의존 관계를 주입한다. 코드는 간결해지지만 외부에서 접근이 불가능하다는 단점이 있다. 그래서 주로 테스트 코드를 작성할때 쓰인다고 한다. 

3. Setter 주입

주입받는 객체가 변경될 가능성이 있는 경우에 사용한다. 다만 실제 변경이 필요한 경우는 매우 드물다고 한다. 

4. 일반 Method 주입

Setter주입과 같다고 보면 되고 거의 사용하지 않는다. 

 

이 중 생성자 주입이 권장됩니다. 

 

생성자 주입이 권장되는 이유는 아래와 같습니다 

 

1. 객체의 불변성 확보 

변경의 가능성을 배제하고 불변성을 보장하는 것이 좋다. 왜냐면 실제로는 의존 관계 변경이 필요한 경우가 극히 드물기 때문이다. 

2. 테스트 코드의 작성

순수 자바 코드로 테스트를 작성하는 것이 좋다고 한다. 왜냐면 테스트는 특정 프레임워크에 의존하지 않는 것이 좋기 때문이다. 그런데 생성자 주입이 아닌 다른 주입은 그렇지 순수 자바 코드로 테스트를 작성하기 어렵다고 한다. 

3. final 키워드 작성 및 Lombok과의 결합

생성자 주입을 사용하면 final 키워드를 필드에 쓸 수 있기에 컴파일 시점에 누락된 의존성을 확인할 수 있다. 그리고 Lombok과 결합하여 손쉽게 쓸 수 있다고 한다. 

4. 스프링에 비침투적인 코드 작성

프레임워크는 비즈니스 로직을 작성하는 서비스 계층에서는 알아야 할 대상이 아니다. 스프링 프레임워크 없이 코드가 작성되면 더욱 유연한 코드를 확보할 수 있다. 그래서 생성자 주입이 더 좋은 것이다. 

5. 순환 참조 에러 방지 

생성자 주입을 사용하면 어플리케이션 구동 시점(객체의 생성 시점)에 순환 참조 에러를 예방할 수 있다. (순환 참조 에러가 발생하면 서로를 계속 호출해서 메모리에 함수의 CallStack이 계속 쌓여서 StackOverFlow에러가 발생한다.)
생성자 주입은 애플리케이션 구동 시점 즉 객체의 생성 시점에 이 에러가 발생하기 때문에 미리 예방할 수 있는 것이다. Bean에 등록하기 위해 객체를 생성하는 과정에서 순환 참조가 먼저 발생하기 때문이다. 필드 주입에선 애플리케이션 구동 시점에 이 에러가 발생하지 않는데 그것이 빈의 생성과 조립(@Autowired) 시점이 분리되어있기 때문이다. 하지만 생성자 주입은 객체의 생성과 조립(의존관계 주입)이 동시에 실행되서 에러를 사전에 잡을 수 있다. 

 

결론적으로 외부에서 객체를 주입받으며 애플리케이션 실행 시점에 필요한 객체(빈)을 생성하고 의존성이 있는 두 객체를 연결하기 위해 한 객체를 다른 객체로 주입시켜야 합니다. 그리고 이때 어떤 객체를 사용할지에 대한 책임이 프레임워크에게 넘길 수 있습니다. 본인은 수동적으로 주입받는 객체를 사용할 것이기 때문입니다. 이 개념이 제어의 역전 즉 IOC라고 부릅니다. 해당 개념을 바로 아래서 살펴봅니다. 

 

IOC 란?

Inversion Of Control의 약자입니다. 제어의 주체가 역전되는 패턴을 의미합니다. 

 

즉 제어는 관리라고 바꿔 부를 수 있다. 특정 객체의 생명주기나 메서드의 호출을 직접 관리한다는 의미로 이해할 수 있다. 그리고 역전은 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 말한다. 

 

즉 메소드나 객체의 호출작업이 개발자가 아니라 외부에서 결정되는 것을 의미한다. 

 

IOC는 역할과 책임을 분리해서 결합도를 낮추고 응집도를 높인다. 즉 객체지향 원칙을 잘 지키기 위해서 사용한다. 

 

IOC Container란 IOC를 구현하는 프레임워크입니다. 즉 제어권을 컨테이너가 가져가고 그 컨테이너로 객체를 관리하고 객체의 생성에 관여하는 등의 의존성을 관리하는 것입니다. 인스턴스의 생성부터 소멸까지의 인스턴스 생명주기 관리를 개발자가 하는 것이 아니라 컨테이너가 대신 해주는 것이다. 그래서 개발자는 로직에 집중할 수 있다. 

 

IOC는 크게 두 분류로 나뉜다. 

 

1. Dependency Lookup

저장소에 있는 Bean에 접근하기 위해 컨테이너가 제공하는 API를 이용해 Bean을 LookUp하는 것이다.

2. Dependency Injection

각 클래스 간의 의존관계를 빈 설정(Bean Definition) 정보를 바탕으로 컨테이너가 자동으로 연결해주는 것 

 

그리고 DL은 컨테이너 종속이 증가하기 때문에 주로 DI를 쓴다고 한다. 

 

IOC컨테이너=스프링 컨테이너 

스프링 컨테이너가 관리하는 객체를 Bean이라고 부르고 이 Bean들을 관리한다는 의미에서 컨테이너를 BeanFactory라고 부른다. 그리고 BeanFactory에 여러가지 컨테이너 기능을 추가할 것을 ApplicationContext라고 부른다. 

 

BeanFactory ApplicationContext
BeanFactory 계열의 인터페이스만 구현한 클래스는 단순히 컨테이너에서 객체를 생성하고 DI를 처리하는 기능만 제공한다.  Bean을 등록, 생성, 조회, 반환 관리하는 기능은 BeanFactory와 같다. 
Bean을 등록, 생성, 조회, 반환 관리를 한다.  스프링의 각종 부가 기능을 추가로 제공한다. 
팩토리 디자인 패턴을 구현한 것으로 BeanFactory는 빈을 생성하고 분배하는 책임을 지는 클래스이다.  국제화가 지원되는 텍스트 메시지를 관리해준다. 
Bean을 조회할 수 있는 getBean() 메소드가 정의되어있다.  이미지같은 파일 자원을 로드할 수 있는 포괄적인 방법을 제공해준다. 
보통은 BeanFactory를 바로 사용하지 않고, 이를 확장한 ApplicationContext를 사용한다.  리스너로 등록된 빈에게 이벤트 발생을 알려준다. 

 

DI Container 란? 

외부에서 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 DI 컨테이너라고 합니다. 

IOC 컨테이너라고도 하지만 의존관계 주입에 초점을 두어 DI컨테이너라고 부릅니다. 

 

면접 예상 질문 

1. BeanFactory가 무엇인가요? 

2. DI를 사용해야 하는 이유를 하나 이상 말해보세요. 

3. 의존성 주입 방식 중 아는 것을 두가지 이상 말해보세요. 

4. ApplicationContext란 무엇인가요? 

5. IOC는 크게 두 가지 방식이 있는데 각각 무엇인가요? 

6. 생성자 주입이 권장되는 이유에 대해서 한가지 이상 말해보세요. 

7. 의존성이란 무엇인가요? 

8. DI는 의존성 분리를 위해 어떤 원칙을 준수하나요? 

9. 프레임워크에 비침투적인 코드란 어떤 개념을 의미하는지 테스트 코드 작성 관점에서 설명해보세요. 

10. DI Container란 무엇인가요? 


아래 출처에서 많은 도움을 받았습니다. 

 

감사합니다. 

 


의존관계 역전 원칙 

- https://ko.wikipedia.org/wiki/%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84_%EC%97%AD%EC%A0%84_%EC%9B%90%EC%B9%99

 

생성자 주입이 권장되는 이유 

- https://mangkyu.tistory.com/125

 

DI란

- https://medium.com/@jang.wangsu/di-dependency-injection-%EC%9D%B4%EB%9E%80-1b12fdefec4f 

- https://mangkyu.tistory.com/150

 

우테코 DI / IOC 

- https://www.youtube.com/watch?v=8lp_nHicYd4 

 

IOC 컨테이너 

- https://dev-coco.tistory.com/80

 

Spring IoC/ DI 컨테이너

- https://codingnotes.tistory.com/60