본문 바로가기
Spring

스프링 빈에 관하여

by hseong 2023. 6. 30.

Bean

Spring에서 Bean이란 Spring IoC 컨테이너에 의해 관리되는 객체를 말합니다. Spring bean은 컨테이너에 의해 인스턴스화되고, 의존성을 주입받고, 생애주기동안 관리됩니다.

스프링 컨테이너는 하나 이상의 빈을 관리합니다. 이러한 빈은 컨테이너에 제공하는 configuration metadata를 통해 생성됩니다.

Bean definition과 Configuration metadata

<https://docs.spring.io/spring-framework/reference/core/beans/basics.html#beans-factory-metadata>

Configuration metadata는 개발자가 스프링 컨테이너에게 애플리케이션의 객체를 어떻게 인스턴스화, 구성, 조합하는지에 관한 관한 방법을 나타냅니다.

이러한 Configuration metadata는 xml 또는 자바 기반으로 작성할 수 있습니다.

작성한 metadata는 ApplicationContext에 전달하면 설정 정보를 읽어들여 빈을 정의합니다.

컨테이너 내부에서 이러한 빈 정의는 BeanDefinition 객체로 표현되어 스프링 컨테이너는 이러한 설정 정보가 자바 코드인지, XML 인지 전혀 알 필요 없이 오직 BeanDefinition만 알면 됩니다. 그리고 이러한 BeanDefinition으로부터 스프링 빈을 생성하게 됩니다.

빈 스코프

스프링 컨테이너는 싱글톤, 프로토타입과 4개의 웹 스코프까지 총 6개의 빈 스코프를 제공합니다.

싱글톤

싱글톤 스코프는 스프링 빈의 기본 스코프입니다. 컨테이너별로 단 하나의 인스턴스만 생성되며 해당 빈에 대한 모든 요청과 참조는 캐시된 객체를 반환합니다.

GoF의 싱글톤 패턴과의 주요한 차이점은 싱글톤 패턴의 경우 클래스 로더 별로 단 하나의 인스턴스가 생성된다는 것을 보장한다면, 스프링은 컨테이너 별로 단 하나의 인스턴스가 생성된다는 것을 보장한다는 점입니다.

https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-singleton

기본적으로 스프링 컨테이너는 초기화 프로세스의 일부로 모든 싱글톤 빈을 생성하고 구성합니다. 이를 통해서 빈의 구성에 있어서 발생할 수 있는 오류를 즉시 발견할 수 있습니다.

컨테이너에 빈의 지연 초기화를 지시할 수도 있습니다. 그러나 다른 싱글톤 빈이 해당 지연 초기화 빈에 대한 의존성을 가지고 있다면 컨테이너는 지연 초기화 빈을 애플리케이션 초기화 시점에 생성하여 주입합니다.

프로토타입

프로토타입 스코프는 특정 빈에 대한 요청이 있을 때마다 새로운 빈 인스턴스를 생성하고 획득합니다.

일반적으로 상태를 가지는 빈들은 프로토타입 스코프를 사용하고, 상태를 가지지 않는 빈들은 싱글톤 스코프를 사용해야 합니다.

https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html#beans-factory-scopes-prototype

프로토타입 빈은 컨테이너에 의해 전체 라이프사이클이 관리되지 않습니다.

빈 객체를 인스턴스화하고, 의존성을 주입한 뒤 클라이언트에게 전달하면 싱글톤 빈과는 달리 마치 일반적인 객체처럼 클라이언트가 라이프사이클을 관리해야 합니다.

단순히 생각해보면 자바에서 new 연산자를 컨테이너가 대체한다고 볼 수 있습니다.

싱글톤 빈과 프로토타입 빈

만일 싱글톤 빈이 프로토타입 빈에 대한 의존성을 가지고 있다면 우리가 원치 않는 방식의 동작이 수행될 것입니다.

무상태의 싱글톤 빈 A가 상태를 가진 비싱글톤 빈 B를 사용한다고 가정해보겠습니다.

  • 개발자는 A의 특정 메서드를 호출할 때마다 새롭게 생성된 B 인스턴스에 의해서 작업이 처리되기를 원할것입니다. 그러나 싱글톤 빈은 애플리케이션 초기화 단계에 인스턴스화 되고 종속성이 주입될 것입니다.
  • 이때, A인스턴스화 되면서 프로토타입 빈 B도 인스턴스화되어 주입이 이루어질 것입니다. 우리가 원하는 것은 매 호출마다 새로운 인스턴스 B가 생성되는 것이지만, A는 기존에 가지고 있는 인스턴스 B를 이용하여 작업을 수행할 것입니다.

만일 매번 새로운 인스턴스가 생성되길 원한다면 applicationContext.getBean()을 통해 직접 빈을 조회해오거나 다음과 같은 메서드 주입을 이용해야 합니다.

Lookup Method Injection

스프링 프레임워크는 CGLIB라이브러리를 이용하여 바이트코드를 조작, 동적으로 메서드를 override하는 자식 클래스를 생성하여 메서드 주입을 구현합니다.

비싱글톤 빈 B를 사용하는 싱글톤 빈 A에서 B를 주입받기 위해서는 다음과 같이 @Lookup을 이용할 수 있습니다.

public abstract class A {
    public Object process(int input) {
        B b = createB();
        b.setInput(input);
        return b.execute();
    }

    @Lookup
    protected abstract B createB();
}

또다른 방법으로는 @Configuration클래스에서 다음과 같이 정의될 수도 있습니다.

@Bean
@Scope("prototype")
public B b() {
    return new B();
}

@Bean
public A a() {
    return new A() {
        protected B createB() {
            return b();
        }
    }
}

@Bean@Configuration

@Configuration는 해당 클래스의 주요 목적이 bean definition 소스임을 나타냅니다. 또한, @Configuration을 사용하면 동일한 클래스에서 다른 @Bean메서드를 호출하여 빈 간의 종속성을 정의할 수 있습니다.

어떻게 싱글톤 객체가 만들어지는가?

모든 @Configuration 클래스는 애플리케이션 실행시에 바이트코드를 조작하는 라이브러리인 CGLIB를 통해 자식 클래스가 만들어집니다.

자식 클래스는 부모 클래스의 메서드를 호출하기 전에 컨테이너에 캐시된 빈이 있는지 확인합니다.

따라서 @Configuration 클래스는 final로 선언되서는 안 됩니다. @Bean 메서드 또한 final, private으로 선언되서는 안 됩니다.

또한 @Configuration이 아닌 곳에서 등록한 @Bean은 바이트코드 조작이 이루어지지 않기 때문에 싱글톤을 보장하지 않습니다.

참고: 스프링 공식문서 The IoC Container