5 min read

<Spring> 스프링에서 왜 DI가 중요할까?

우선 DI를 파보기 전에 스프링이 뭔지에 대해서 알아봅시다.

스프링이라 함은 오픈 소스의 경량 웹 프레임워크라고 정의되어있습니다.즉,개발자들이 웹 개발을 좀 더 편하게 하기위해서 공개된 코드의 클래스나 인터페이스를 상속 및 구현하여 실행하는것이라고 볼 수 있습니다.

사실 이렇게 얘기하면 스프링을 처음 접하시는 분들은 붕 뜨는 느낌을 받으실 것입니다.일단 논리적인 정의는 위와 같지만 직접 코드를 쳐보시면서 구현해보면 조금씩 개념이 구체화됩니다.

이번 포스팅에서는 저러한 추상적인 개념을 구체화하는 첫 단계로 스프링에서 가장 핵심 개념인 Dependency Injection에 대해 공부해봅시다.

Dependency Injection(의존성 주입)

  • 줄여서 DI라고 불리우는 이 개념은 IoC(Inversion of Control)와 함께 언급됩니다.IoC는 의역하면 “어떠한 행위의 주체가 행위의 대상에 대한 제어권을 상실했다”라고 이해하셔도 됩니다.이러한 IoC를 구현하기위한 한가지 패턴 중 하나가 DI인것입니다.
  • 의존성 주입을 알아보기 전에 의존성이 무엇인지부터 봅시다.
  • 아래의 코드와 비슷한 구조를 가진 코드를 시중의 스프링 책 또는 강의를 한번이라도 봤으면 보셨을 것입니다.
public class ClubService{
	private final FileClubRepository fileClubRepository;

	public ClubService(){
		this.fileClubRepository = new FileClubRepository();
	}

	public void create(...){
		fileClubRepository.save(...);
	}
}
  • 지금 만들려는 프로그램에서는 ClubService 클래스가 있고 이 클래스가 Club의 목록을 관리하는 기능을 제공한다고 가정합니다.그래서 생성된 클럽을 저장하기 위해 FileClubRepository를 사용합니다.
  • 위와 같은 상황에서 ClubService라는 클래스는 FileClubRepository 없이는 제 기능을 수행하지 못합니다.즉,ClubService는 FileClubRepository에 의존하고 있습니다.
  • 의존성이라는 개념이 살짝 잡히시나요?의존하는 대상이 주체에게 없을 경우 주체는 스스로의 제 기능을 수행하지 못한다고 생각하시면 됩니다.
  • 위 코드를 기반으로 main메서드에서 ClubService를 생성해 봅시다.
public static void main(String[] args){
	ClubService clubService = new ClubService();
}
  • 현재 file에 Club에 대한 정보를 저장하고있습니다.이러한 상황에서 만약 DB에 해당 정보를 저장하라는 업무가 떨어졌습니다.
  • 그래서 DBClubRepository 클래스를 구현하고 직접 갈아끼우고 난뒤,퇴근했습니다.
  • 그러던 다음날,AWS의 S3 저장소를 이용하자고 업무가 떨어졌습니다.그러면 여러분들은 또 다시 AwsS3ClubRepository 클래스를 만들고 한땀한땀 코딩하면 모든 의존성을 변경해야 할 것입니다.
  • 이러한 수고로움을 덜기위해 의존성 주입이라는 개념이 발생했습니다.의존성 주입이란 이 클래스가 의존하는 다른 클래스들을 외부에서 주입시킨다는 뜻입니다.이를 구현하는 방법에는 총 4가지가 있지만 대표격인 생성자주입을 알아봅시다.다른 방법은 참고하세요.
public class ClubService{
	private final ClubRepository clubRepository;

	public ClubService(ClubRepository clubRepository){
		this.clubRepository = clubRepository;
	}

	public void create(){
		clubRepository.save(...);
	}
}
  • 위와 같이 한 오브젝트가 의존하는 오브젝트를 생성하는 것이 아니라 외부에서 넘겨받는 것을 DI(의존성 주입)라고 합니다.아래의 main메서드처럼 ClubService 오브젝트를 생성할 때 ClubRepository의 구현부를 넘겨줌으로써 의존성을 주입합니다.참고로 현재 ClubRepository는 인터페이스입니다.
  • 만약 해당 구조가 쉽게 이해가 안가시면 다형성에 대한 이해가 부족한것이니 참고하시길 바랍니다.
public static void main(String[] args){
	ClubRepository clubRepository = new AwsS3ClubRepository();
	ClubService clubService = new ClubService(clubRepository);
}
  • 지금까지 의존성 주입이 무엇이고 왜 필요한지에 대해 배웠습니다.생각보다 복잡하지도 않았고 코드 조금만 쳐도 누구나 할 수 있을것 같지 않나요?그러면 구태여 스프링까지 써가면서 의존성 주입을 해주어야하는걸까요?
  • 위의 말처럼 규모가 굉장히 간단한 프로그램의 경우에는 한땀한땀 코딩으로 의존성 주입을 해줄 수 있을것입니다.하지만 의존하는 클래스가 수십개인 클래스가 수십개가 있다고 생각해보셨나요?직접 하다보면 실수가 발생할것만 같습니다.
  • 이러한 번거로움을 해결해주는 것이 스프링의 컨테이너입니다.스프링 DI 컨테이너의 경우 ComponentScan,Autowired 등을 사용하여 수십 수백개의 클래스들의 의존성을 개발자 대신하여 연결해 줍니다.
@Service
public class ClubService{
	private final ClubRepository clubRepository;

	@Autowired
	public ClubService(ClubRepository clubRepository){
		this.clubRepository = clubRepository;
	}

	public void create(){
		clubRepository.save(...);
	}
}
  • @Autowired는 스프링 빈으로 등록된 ClubRepository 타입의 빈을 자동으로 찾은 뒤 해당 생성자의 매개변수로 받습니다.
  • Lombok을 합칠 경우 아래와 같이 더욱 짧게 변경될 수 있습니다.
@Service
@RequiredArgsConstructor
public class ClubService{
	private final ClubRepository clubRepository;

	public void create(){
		clubRepository.save(...);
	}
}
  • 만약 스프링의 컴포넌트 스캔과 언급된 어노테이션들이 궁금하실경우 참고하시길 바랍니다.
  • 이상으로 DI가 필요한 이유에 대해서 알아보았습니다.