본문 바로가기
IT 개인 공부/Spring

[Spring] Bean & DI & IoC 컨테이너

by Libi 2021. 8. 13.
반응형

스프링의 특징 중 하나는 바로 의존성 주입(Dependency Injection, DI)이다.

계층이나 서비스들 간에 의존성이 존재할 경우 스프링 프레임워크가 서로를 알아서 연결해주기 때문에 개발자가 따로 연결할 필요가 없으며 높은 재사용성과 가독성이 있는 코드를 만들어낼 수 있는 장점이 있다.

그렇다면 스프링 프레임워크에서 어떤 친구가 DI를 제공해줄까?

바로 IoC 컨테이너라는 친구가 이러한 기능을 제공해준다. 객체를 직접 생성하지 않고 스프링 프레임워크에 등록하면 IoC 컨테이너가 원하는 곳에 등록된 객체를 주입하여 객체 간의 의존성을 만들어준다.

 

그럼 IoC 컨테이너는 무엇인지에 대해 먼저 알아보자.

IoC(Inversion of Control)는 스프링의 특징으로 제어권이 개발자에게 있는 것이 아니라 스프링 프레임워크에게 있다는 것을 의미한다. 즉, 의존성 주입을 스프링 프레임워크가 제어해 준다는 것을 뜻한다.

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

즉, IoC 컨테이너는 스프링 컨테이너라고도 불리며 IoC 방식으로 인스턴스의 생명주기, 생성, 관계 설정, 사용 등을 관리하는 컨테이너이다.

IoC 컨테이너는 두 가지 종류로 나뉜다.

BeanFactory

  • 스프링 설정 파일에 등록된 Bean 객체를 생성하고 관리
  • 디자인 패턴의 팩토리 패턴을 구현함
  • 컨테이너가 구동될 때 Bean 객체를 생성하는 것이 아니라 클라이언트의 요청에 의해서 Bean 객체가 사용되는 시점(Lazy Loading)에 객체를 생성하는 방식
  • 일반적으로 잘 사용하지 않음

ApplicationContext

  • BeanFactory와 마찬가지로 Bean 객체를 생성하고 관리
  • BeanFactory와 달리 컨테이너가 구동되는 시점(Pre Loading)에 객체를 생성하는 방식
  • DI, IoC 이외에도 다양한 기능들을 제공
  • 주로 사용하는 방식

즉, IoC 컨테이너는 스프링 빈(Spring Bean)이라는 객체를 관리하여 자동으로 의존성을 주입해준다고 할 수 있다.

스프링 빈은 간단하게 얘기하면 스프링 컨테이너에 의해서 생성된 자바 객체를 뜻한다.

또한, 이 말은 IoC 컨테이너에 등록된 Bean 객체끼리만 DI가 가능하다는 것을 의미한다. 내가 직접 생성한 객체로는 DI가 불가능하다는 뜻이다.

그렇다면 애플리케이션이 실행되었을 때 Bean 객체를 IoC 컨테이너에 등록하는 방법은 무엇일까?

크게 두 가지로 나뉜다. 첫 번째는 컴포넌트 스캔(Component Scan) 방식이고 두 번째는 xml 파일 or 자바 설정 파일에 직접 등록하는 방식이다. 스프링 입문 단계이기 때문에 간단하게만 짚고 넘어가겠다.

컴포넌트 스캔 방식은 간단하다. Bean으로 등록하고 싶은 클래스에 @Component 어노테이션을 달아주면 된다.

​스프링으로 웹 애플리케이션을 개발하다 보면 흔히 @Controller, @Service, @Repository 어노테이션을 자주 사용할 것인데 이들 또한 마찬가지로 자동으로 IoC 컨테이너에 등록된다.

그 이유는 이들의 인터페이스를 타고 들어가 보면 @Component 어노테이션이 존재하기 때문이다.

 

다음으로는 xml 파일이나 자바 설정 파일에 직접 등록하는 방법이다. xml 파일로 등록하는 방법은 최근에는 잘 사용하지 않기 때문에 생략하고 자바 설정 파일에 등록하는 방법을 알아보도록 하겠다.

자바 설정 파일에 등록하는 방법은 클래스를 하나 생성하고 @Configuration 어노테이션을 등록해 준 후 원하는 객체를 반환하는 메서드에 @Bean 어노테이션을 등록해주면 된다.

 

위와 같이 작성한다면 MemberService와 MemberRepository에 @Service, @Repository 어노테이션을 달아준 것과 동일하게 자동으로 IoC 컨테이너에 Bean 객체가 등록된다.

당연히 컴포넌트 스캔 방식이 훨씬 단순하고 편리한 방법이다. 하지만 상황에 따라 적절하게 두 가지 방법을 활용해야 하기 때문에 모두 알아놓는 것이 좋다.

참고로 실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용하고 정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하는 경우 자바 설정 파일 등록 방식을 사용한다고 한다.

애플리케이션이 실행되면 이러한 방법들을 통해 자동으로 IoC 컨테이너에 Bean 객체들이 등록되어 필요할 때마다 객체를 여러 번 생성하지 않고 등록된 객체(싱글톤 패턴)를 호출해서 서로 의존성을 연결해준다.

스프링 프레임워크에서 자동으로 Bean 객체를 제어해주기 때문에 IoC 컨테이너라고 불리는 것이다.

마지막으로 의존성을 주입하기 위해 IoC 컨테이너에 등록된 Bean 객체를 받아서 주입하는 방법을 알아보자.

​대표적인 DI 방법으로는 필드 주입, setter 주입, 생성자 주입 이렇게 3가지 방식이 있다.

먼저 드 주입 방식이다. 가장 단순한 방식으로 멤버 변수에 @Autowired 어노테이션을 사용하여 주입받는 방식이다.

필드 주입 방식은 다른 방식들에 비해 코드가 가장 단순한 방식이다.

하지만 한번 필드 주입되면 외부에서 변경이 불가능하다.

따라서 순수한 자바 테스트 코드에서는 테스트가 불가능하며, @SpringBootTest 어노테이션처럼 스프링 컨테이너를 이용하는 테스트에서만 테스트가 가능하다는 단점이 있다.

 

두 번째는 setter 주입 방식이다. @Autowired 어노테이션을 사용하고 setter 메서드를 통해 객체에 값을 삽입해주는 방식이다.

보통 스프링 빈은 한 번 등록되면 애플리케이션 생명주기 동안 바뀌지 않는 편인데 setter 메서드를 사용하기 때문에 언제든지 값이 변경될 수 있다는 단점이 존재한다. 

따라서 setter 주입 방식은 선택, 변경 가능성이 있는 의존관계에 사용된다.

 

마지막으로 생성자 주입 방식이다. @Autowired 어노테이션을 사용하고 생성자 생성 시 매개변수로 주입받을 객체를 넣어주는 방식이다.

빈을 등록하기 위한 생성자 호출 시점에 딱 1번만 호출되는 것이 보장됨으로 불변, 필수 의존관계에 사용된다.

또한, 생성자가 1개만 존재한다면 @Autowired 어노테이션을 생략해도 스프링에서 자동으로 주입해준다.

 

그렇다면 어떤 방식을 사용해야 할까? 생성자 주입 방식을 사용하자!

대부분의 의존관계는 애플리케이션 생명주기동안 한번 의존관계가 맺어지면 변경될 일이 없다. 하지만 setter 주입 방식을 사용하면 개발자가 실수로 의존관계를 변경할 수 있는 가능성이 열리게 된다.

또한, 필드 주입방식은 테스트하기가 어렵다는 단점이 존재한다.

마지막으로 생성자 주입 방식은 final 키워드를 사용할 수 있다는 강력한 장점이 존재한다.

의존관계를 주입할때 개발자의 실수로 모든 의존관계를 주입하지 못할 수 도 있다. 이때 프로그램을 실행하게 되면 NPE가 발생하게 된다.

필드 주입 방식과 setter 주입 방식은 생성자를 호출한 이후 DI가 이루어지기 때문에 final 키워드를 사용할 수 없다. 따라서 컴파일 시점에서는 오류를 찾을 수 없으며 런타임 시점에 오류가 발생하게 된다.

하지만 생성자 주입방식은 생성자 호출 시에 DI가 이루어지기 때문에 final 키워드를 사용해서 DI가 누락되었을 경우 컴파일 시점에서 오류가 발생하여 해결할 수 있다.

 

 

[ Reference ]

· https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

반응형

댓글