[Java] DI (의존성 주입) / IoC (제어의 역전)

2023. 1. 6. 16:02기술 창고/Java

728x90
SMALL

스스로 프로젝트를 진행하면서 코드를 짜다보면 명확하게 모르고 두루뭉실 아는 상태에서 사용하는 코드들 혹은 개념들이 많다고 느낀다.

그 중에서 DI는 가장 흔히 사용하고 있음에도 불구하고 뭔지 잘 모르고 있는 것 같아 정리해야할 필요가 있어보인다.

 


IoC (제어의 역전)

IoC를 해석하게 된다면 '제어의 역전' 이라고 부르는데, 이는 개발적인 개념에서 객체의 생성, 생명 주기의 관리까지 모든 객체에 대한 제어권이 바뀌었다는 것을 뜻한다.

Spring을 사용한다면 모든 객체에 대한 제어권은 개발자인 본인이 가지고 있는 것이 아니라 Spring이 알아서 관리하고 제어한다는 것이다.

 

IoC 컨테이너

컨테이너라는 것은 보통 객체의 생명주기를 관리하고, 생성된 인스턴스들에게 추가적인 기능들을 제공하도록 하는 것이다.

스프링 프레임워크도 객체를 생성하고 관리하고 책임지고 의존성을 관리해주는 컨테이너가 있는데,

그것이 바로 IoC 컨테이너(=스프링 컨테이너) 이다.

 

앞서 말했던 것처럼 인스턴스(객체) 생성부터 소멸까지의 인스턴스(객체) 생명주기 관리를 개발자가 아닌 컨테이너가 대신 해준다.

객체관리 주체가 프레임워크(Container)가 되기 때문에 개발자는 로직에 집중할 수 있는 장점이 있다.

 

 

IoC 분류

IoC를 분류하게 되면 DL(의존성 검색) , DI(의존성 주입) 두 가지로 볼 수 있다.

  • DL : 저장소에 저장되어 있는 Bean에 접근하기 위해 컨테이너가 제공하는 API를 이용하여 Bean을 검색하는 것
  • DI : 각 클래스간의 의존관계를 빈 설정(Bean Definition) 정보를 바탕으로 컨테이너가 자동으로 연결해주는 것
    • Setter Injection (수정자 주입)
    • Constructor Injection (생성자 주입)
    • Method Injection (필드 주입)

#  DL을 사용하게 된다면 컨테이너 종속이 증가하기 때문에 주로 DI를 사용한다고 한다.

# 컨테이너 종속이 증가하는 이유 : 컨테이너가 제공해주는 API를 계속해서 이용을 하기 때문에

 

 

DI (의존성 주입)

앞서 말했던 것처럼 DI는 의존성 주입이라고 말한다.

빈으로 설정한 정보들을 바탕으로 각 클래스들의 의존관계를 설정해 컨테이너가 자동으로 주입하여 연결해주는 것이다.

주입 방법에는 3가지가 있다.

 

 

▶생성자 주입

@Controller
public class CocoController {
  //final을 붙일 수 있음
  private final CocoService cocoService;
 
  @Autowired 
  public CocoController(CocoService cocoService) {
      this.cocoService = cocoService;
  }
}

@Controller는 Bean 객체에 포함되는 어노테이션이다.

(@Controller 이외에도 Bean에 포함되는 어노테이션들이 있다.)

Bean들끼리 의존성 주입을 할 때 @Autowired를 통해 주입을 하곤 한다.

 

@Autowired 를 통해 생성자를 주입하여 CocoService를 의존한다.

생성자 주입 시 @Autowired를 통해 자주 주입하곤하는데, 의존하고자 하는 객체에 final이 붙어있다면 @Controller 밑에 @RequiredArgsConstructor를 추가하여 작성한 생성자 부분을 없애주는 방법도 있다.

 

@RequiredArgsConstructor는 final 이 붙은 객체에 한하여 일일히 생성자를 코드로 작성하지 않아도 생성자 주입이 될 수 있게끔 하는 어노테이션이다.

코드의 양을 줄여줌과 동시에 가독성도 올라가기 때문에 자주 사용하기도 한다.

@Controller
@RequiredArgsConstructor
public class CocoController {
  // final을 붙인 객체를 생성자 주입 코드 없이 바로 주입
  private final CocoService cocoService;

}

 

 

▶수정자 주입

수정자 주입은 선택적인 의존성을 사용할 때 유용하다. 혹은 상황에 따른 변경사항이 있을 경우에 의존성 주입이 가능하다.

@Component
public class SampleController {
    private SampleService sampleService;
 
    @Autowired
    public void setSampleService(SampleService sampleService) {
        this.sampleService = sampleService;
    }
}

@Component 는 Bean 객체에 포함되는 어노테이션이다.

set 메소드를 통해 의존성을 주입한다.

Setter Injection으로 의존관계 주입은 런타임시에 할 수 있도록 낮은 결합도를 가지게 구현되었다.

 

하지만 Setter Injection을 통해서 Service의 구현체를 주입해주지 않아도 Controller 객체는 생성이 가능하다.

Controller 객체가 생성가능하다는 것은 내부에 있는 Service의 method 호출이 가능하다는 것인데,
set을 통해 Service의 구현체를 주입해주지 않았으므로, NullPointerException 이 발생한다.

주입이 필요한 객체가 주입이 되지 않아도 얼마든지 객체를 생성할 수 있다는 것이 문제다.

 

 

 

▶필드 주입

단순히 객체 선언부에 @Autowired 를 붙여 주입하는 방식이다.

@Component
public class SampleController {
    @Autowired
    private SampleService sampleService;
}

Field Injection을 사용하면 안되는 이유

  • 단일 책임(SRP)의 원칙 위반

의존성을 주입하기가 쉽다.
@Autowired 선언 아래 개수 제한 없이 무한정 추가할 수 있으니 말이다.

여기서 Constructor Injection을 사용하면 다른 Injection 타입에 비해 위기감을 느끼게 해준다.

Constructor의 parameter가 많아짐과 동시에 하나의 Class가 많은 책임을 떠안는다는 걸 알게된다.

이때 이러한 징조들이 Refactoring을 해야한다는 신호가 될 수 있다.

 

  • 의존성이 숨는다.

DI Container를 사용한다는 것 Class가 자신의 의존성만 책임진다는게 아니라 제공된 의존성 또한 책임진다.

그래서 Class가 어떤 의존성을 책임지지 않을 때, 메서드나 생성자를 통해(Setter나 Constructor) 확실히 커뮤니케이션이 되어야한다.

하지만 Field Injection은 숨은 의존성만 제공해준다.

 

  • DI Container의 결합성과 테스트 용이성

DI Framework의 핵심 아이디어는 관리되는 Class가 DI Container에 의존성이 없어야 한다.

즉, 필요한 의존성을 전달하면 독립적으로 Instance화 할 수 있는 단순 POJO여야한다.

DI Container 없이도 Unit Test에서 Instance화 시킬 수 있고, 각각 나누어서 테스트도 할 수 있다.

Container의 결합성이 없다면 관리하거나 관리하지 않는 Class를 사용할 수 있고, 심지어 다른 DI Container로 전환할 수 있다.

하지만, Field Injection을 사용하면 필요한 의존성을 가진 Class를 곧바로 Instance화 시킬 수 없다.

 

  • 불변성(Immutability)

Constructor Injection과 다르게 Field Injection은 final을 선언할 수 없다.
그래서 객체가 변할 수 있다.

 

  • 순환 의존성

Constructor Injection에서 순환 의존성을 가질 경우 BeanCurrentlyCreationExeption을 발생시킴으로써 순환 의존성을 알 수 있다.

728x90
반응형
LIST

'기술 창고 > Java' 카테고리의 다른 글

[Java] Java Map 내부 구현 파악  (0) 2023.01.06
[Java] Garbage Collector  (0) 2023.01.06
[Java] Call by Value / Call by Reference  (0) 2023.01.06
[Java] 스택(Stack) / 큐(Queue)  (0) 2023.01.06
[Java] LinkedList  (0) 2023.01.05