Dev/Springboot

Spring의 DI - 코드를 유연하고 단순하게 하는 방법

린네의 2024. 5. 6. 18:56

이전 AOP에 이어 Spring POJO의 또 다른 키워드인 DI와 IoC에 대한 개념을 함께 정리해볼까 한다.

AOP에 대한 게시글이 궁금하다면 아래 링크를 참조 바란다.

 

2024.05.06 - [개발/springboot] - Spring의 AOP - 공통 소스 파일을 분리하여 관리할 수 있는 방법

 

Spring 의 AOP - 공통 소스 파일을 분리하여 관리할 수 있는 방법

java OOP 관련 게시글을 작성하다가 Spring에서 필수적으로 알아야 하는 AOP/IoC/DI/PSA에 대해 정리한 글을 공유하면 좋을 것 같아 글을 남겨 본다.  그중에서도 이번 게시글은 AOP에 관련된 내용이다.

zigo-autumn.tistory.com

 

 

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

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

 

객체들 간에 관계가 맺어지거나, 클래스 간에 강하게 결합되어 있을 경우 코드의 수정이나 재사용 시 문제점이 발생한다. 이것을 해결하기 위해 Spring에서는 DI라는 개념을 제공한다. Spring은 특정 위치로부터 클래스를 탐색하고, 객체를 만들며 객체들의 관계까지 설정해 준다. 이러한 이유로 스프링은 DI 컨테이너라고도 불린다. 이때어떠한 객체를 사용할지에 대한 책임은 프레임워크에게 넘어가고, 개발자는  수동적으로 주입받는 객체를 사용하는 현상이 발생하게 되는데 이를 제어의 역전 ( Ioc : Inversion of Control )이라고 표현한다.

 

정리하면, 강하게 결합된 클래스들을 분리하고 애플리케이션 실행 시점에 객체 간의 관계를 결정해 줌으로써 결합도를 낮추고 유연성을 확 부 해준다. 이러한 방법은 상속보다 훨씬 유연한데, 한 객체가 다른 객체를 주입받으려면 반드시 DI 컨테이너에 의해 관리되어야 한다.

 

  • 코드를 단순화시켜준다
  • 모듈 간의 결합도를 낮추고 유연성을 향상할 수 있다
  • 재사용성을 높여준다
  • 테스트가 용이하다

 

IoC(Inversion Of Control)란?

객체의 생성부터 파괴까지 생명주기 관리와 의존성 관리를 개발자가 아닌 컨테이너에게 넘김으로써 모든 객체에 대한 제어권이 바뀌었다는 것을 의미한다. '제어의 반전'이라고 표현할 수 있다. 

 

스프링 컨테이너를 Ioc컨테이너라고 표현하기도 한다.

 

  • IoC 컨테이너

외부에서 생성된 객체들을 등록하고, 다른 객체에서 등록된 객체와의 의존 관계를 필요로 할 때 컨테이너에 등록된 객체를 외부에서 주입해 주는 역할을 수행한다. 

 

빈 팩토리, DI컨테이너, 애플리케이션 컨텍스트가이에 해당한다. 

 

 

 DI를 실행하기 위해서는 주입시키려는 객체가 빈(Bean)으로 등록되어 있어야 하는데. 스프링 IoC 컨테이너에서 이러한 빈(Bean) 객체를 관리하며, IoC에 등록된 빈(Bean) 객체 만이 의존성 주입의 대상이 될 수 있다.

 

 

  • Bean

Ioc 컨테이너가 관리하여 의존성 관리가 편리하며, 싱글톤 형태를 가진다.  Bean을 Ioc컨테이너에 등록하는 방법은 @Component 방식과 @Configuration 방식으로 나눌 수 있다.

 

각각의 방법은 다음과 같다.

 

@Component를 통해 Bean 객체를 생성하고 IoC컨테이너에 등록할 수 있다.  @SpringbootApplication에는 @ComponentScan이라는 애노테이션이 설정되어 있는데,  @ComponentScan이 설정된 메인함수에서부터 @Component가 설정되어 있는 모든 클래스를 찾아서  Bean으로 등록시켜 준다. 개발자가 직접 컨트롤 가능한 클래스에 사용한다.

 

@Configuration을 통해 Bean으로 설정할 클래스에 선언 후, 특정 타입으로 리턴하는 메서드에 @Bean을 붙여주게 되면 @Bean에 해당하는 메서드 이름으로 빈 등록이 된다. 개발자가 컨트롤이 불가능한 외부 라이브러리를 빈으로 등록할 경우 사용한다.

 

 

 

DI 구현 방법

DI 구현 방법은 필드주입, setter 주입, 생성자 주입으로 나뉜다.

 

  • 필드 주입

사용하고 편리하지만, 하나의 클래스가 많은 책임을 갖게 될 가능성이 높다. 또한 생성자 주입에 비해 의존 관계를 한눈에 파악하기 어렵다.

DI 컨테이너와의 결합도가 커지고 테스트하기 어렵다. 또한 불변성을 보장할 수 없으며 순환 참조가 발생할 수 있다.

 

  • setter 주입 ( 수정자 주입 )

선택적인 의존성을 사용할 수 있지만 주입받지 않은 구현체를 사용하는 메서드에서 NPE가 발생한다. 순환참조문제가 발생할 수 있다.

 

  • 생성자 주입

생성자에 @Autowired 애노테이션을 붙여 의존성을 주입받을 수 있으며 가장 권장되는 주입 방식이다.

의존 관계를 모두 주입해야만 객체 생성이 가능하므로 NPE를 방지할 수 있고, 불변성을 보장할 수 있다.  순환참조 발생 시 컴파일단계에서 발견할 수 있다. 하지만 의존성을 주입하기 번거롭고 생성자 인자가 많아지면 코드가 길어지는 문제가 발생한다.

 

위에서 말하는 순환 참조란 서로 다른 여러 빈들이 서로를 참조하고 있음을 의미한다. 즉 빈들끼리 서로를 의존하고 있는 관계를 의미한다.