컨테이너? 빈?

권 해·2023년 3월 27일
0

Spring

목록 보기
8/9

스프링을 처음 공부할 때부터 수도 없이 들었던 컨테이너와 빈.
정말 자주 접할 수 있는 단어이지만, 그 개념을 아직도 정확히 모르고 있었다.
이대로 넘어가면 안되겠다 싶어서 생각난 김에 제대로 알아보려고 한다.

IoC 컨테이너

스프링은 스프링 컨테이너에 있는 객체를 관리한다. 스프링 컨테이너에 속한 객체를 빈(Bean) 객체라 하는데, 스프링은 빈으로 등록된 모든 객체를 스프링 컨테이너에 생성하고 의존성을 주입한다.

여기서 IoC는 Inversion of Control(제어의 역전)의 약어이다.
제어의 역전 이란 객체의 생성 및 생명주기에 대한 모든 객체에 대한 제어권이 바뀌었다는 것을 의미한다.
쉽게 말해, 개발자가 객체를 제어하지 않고, 스프링이 대신 해준다는 뜻이다.

빈(Bean)

빈은 IoC 컨테이너가 관리하는 자바 객체이다. IoC 컨테이너에 빈이 등록되면, IoC 컨테이너에서 이를 관리해준다.

빈의 생명주기는 다음과 같다.

스프링 IoC 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 메소드 호출 → 사용 → 소멸 전 콜백 메소드 호출 → 스프링 종료

스프링은 보통의 경우 스프링 컨테이너에 빈 인스턴스를 단 한개만 저장하는 싱글톤 방식을 채택하고 있다.
빈 이름은 항상 다르게 지정이 되어야 한다. 예상치 못한 여러 오류가 발생하는데, 이를 개발 중 발견하기 매우 어렵기 때문이다.

스프링 빈을 컨테이너에 등록하는 방법은 여러가지가 있는데, 그 중 가장 많이 사용하는 방법 2가지는 다음과 같다.

첫 번째 방법은 스프링 설정 파일에서 빈을 등록하고 의존성을 주입하는 방법이다. 주로 외부 라이브러리 객체를 빈으로 등록할 때 사용한다.
아래는 자바 설정 파일로 2개의 빈을 등록한 코드이다.

@Configuration
public class ApplicationConfig {
 
    @Bean
    public MemberRepository memberRepository() {
        return new memberRepository();
    }
 
    @Bean
    public MemberService memberService(MemberRepository memberRepository) {
        MemverService memberService = new memberService();
 
        
        memberService.setMemberRepository(memberRepository);
        return memberService;
    }
}

두 번째 방법은 컴포넌트 스캔 기능이다.
컴포넌트 스캔을 사용하면 빈을 직접 등록하지 않고도 클래스에 @Component, @Service, @Controller 등의 애노테이션을 붙여 빈으로 등록하는 방법이다.
보통 직접 만든 클래스는 간편하게 컴포넌트 스캔을 이용해 빈으로 등록한다.

@Component 
public class MemberService{
...
...
}

내가 지금까지 객체를 따로 생성하지 않아도 사용할 수 있엇던 이유는 컨테이너에 빈을 등록하여 대신 생성해주었기 때문이라는 것이다.

그래서 지금까지 작성했던 코드를 확인해서 빈 등록을 해주었는지 봤더니, 전부 컴포넌트 스캔 방법을 통해 등록을 해주었는데, 레포지토리는 빈으로 등록이 되지 않아있던 것이다.
그럼에도 동작에는 문제가 없었다. 어떻게 가능할 수 있었던 것일까?

그 이유를 알아봤더니,JpaRepository 인터페이스를 상속하면 해당 인터페이스의 구현체인 SimpleJpaRepository에서 @Repository를 통해 스프링 컨테이너가 관리하는 빈이 된다고 한다.

이제 컨테이너와 빈이 무엇인지 알았다.
그렇다면, 왜 스프링에서는 컨테이너에게 객체 제어권을 맡기는 방법을 사용하는 것일까?

IoC 컨테이너를 사용하는 이유

  1. 의존성 주입(DI)

DI란 사용자가 객체를 직접 생성하는 게 아니라 외부에서 생성 한 후 주입시켜 주는 것을 의미한다.
한 객체가 어떤 객체에 의존할 것인지는 별도의 관심사이다.
스프링은 의존성 주입을 도와주는 DI 컨테이너로써, 강하게 결합된 클래스들을 분리하고, 애플리케이션 실행 시점에 객체 간의 관계를 결정해 줌으로써 결합도를 낮추고 유연성을 확보해준다.

의존성 주입(DI)의 장점

  • 두 객체 간의 관계라는 관심사의 분리
  • 두 객체 간의 결합도를 낮춤
  • 객체의 유연성을 높임
  • 테스트 작성을 용이하게 함
  1. 빈의 스코프

빈의 스코프란 빈 오브젝트가 만들어져 존재할 수 있는 범위이다.
빈 오프젝트의 생명주기는 스프링 IoC 컨테이너가 관리하기 때문에 대부분 정해진 범위의 끝까지 존재한다.
기본적으로 빈은 싱글톤으로 만들어진다.

사용자의 요청이 있을 때마다 매번 애플리케이션 로직을 담은 오브젝트를 새로 만드는 것은 매우 비효율적이므로, 애플리케이션의 로직을 담은 오브젝트는 대부분 싱글톤 빈으로 만드는 것이 효율적이다.

IoC 컨테이너를 사용하는 이유는 위와 같다.
조금 더 쉽게 예를 들면,
A레포지토리가 있고, A레포지토리에 의존하는(A레포지토리 기능을 사용하는) A서비스가 있다고 하자.

만약 IoC 컨테이너를 사용하지 않으면, A서비스에서는 A레포지토리를 new를 통해 생성하여 주어야 한다.
이렇게 구현하면, 나중에 A서비스에서 사용하는 레포지토리를 B레포지토리로 변경해야 할 때, A서비스의 코드를 수정해 주어야 한다.
위 상황이 바로 두 클래스가 강하게 결합되어 있는 것이다.

이 상황에 IoC 컨테이너를 사용했다며면, A서비스에서 바꿔야 할 부분은 의존성 주입 부분밖에 없다. A레포지토리에서 B레포지토리로 의존성 주입만 해주면, 다른 부분을 바꿀게 없다.
객체의 생성은 컨테이너에게 맡기고, 나는 사용만 하면 되는 것이다.
간단히 말해 관심사를 분리하는 것이라고 할 수 있다.

또한, 객체를 생성할 때도 싱글톤으로 해주기 때문에, 한 서비스가 여러번 사용되어도, 하나의 인스턴스만 컨테이너에서 꺼내서 사용하기 때문에 호출 될때마다 생성될 일이 없다.

이렇게, 컨테이너와 빈, IoC, DI의 관계에 대해 알아보았다.
지금까지 이것도 제대로 모르고 스프링을 사용하고 있었다는게 놀라울 정도이다.
"스프링은 원래 이렇게 쓰는거구나"라고 생각만 했었다.

어노테이션으로 빈은 왜 등록해주었는지, 의존성 주입은 왜 하는건지 이제야 알게 됐다.
Mock 객체를 사용하기 위해서 IoC 컨테이너에 빈을 등록하면 테스트 할때도 모듈간에 의존하지 않고 테스트를 할 수 있다.

참고 자료

0개의 댓글