Spring/Spring Boot

Spring Bean 공부하기 (Been 생명주기, Scope, 권장 사용 방법)

민돌v 2025. 1. 29. 00:54

오늘은 지금까지 잘 모르고 사용했던 Spring Bean 에 대해 공부해보고자 합니다.

 

[목차]

  1. Spring Bean 이란
  2. Bean 의 생명 주기와 Bean Scope
  3. Bean 의 사용 이유
  4. Spring Bean 의 권장 사용 방법

1. Spring 에서의 Bean 이란

Spring Framework 에서의 Bean 은 Spring Boot 의 핵심 기술 중 하나인 제어의 역전(IOC)와 의존성 주입(DI)와 밀첩한 관계를 가집니다.
Spring Framework는 IOC Container 를 통해 객체 주입(생성)에 대한 의존성을 코드 구현부에서 직접 가져가는게 종속성 주입(DI)을 통해 객체의 호출부에서는 어떤 객체가 생성되는지 신경 쓰지 않게하여 객체간의 결합도를 낮춰 설계할 수 있도록 해주는 기술을 제공합니다.

✔️ 이때 Spring IOC Container(org.springframework.context.ApplicationContext) 에서 관리되는 객체를 우리는 Bean 혹은 Bean 객체라고 부릅니다.

 

Pojo 객체를 IOC Contanier 에 Bean 객체로서 관리하도록 지정하는 방법은 @Configuration, @Baen 등의 어노테이션을 통해 런타임 시 객체를 생성해 ApplicationContext 에서 관리하도록 지정할 수 있습니다. 
(참고 : https://docs.spring.io/spring-framework/reference/core/beans/java.html)


2. Bean 생명 주기와 Bean Scope

✔️ Bean Life Cycle

  1. Spring 실행
  2. Spring IOC 컨테이너 생성 (Application Context)
  3. Component Scan → Bean 등록
  4. 의존성 주입
  5. Bean 초기화 콜백 메서드 실행
  6. Spring 종료 전 소멸 콜백 메서드 실행
    (Bean 콜백 메서드에 관해서는 코드를 까보지 않아 잘 모릅니다,, 김영한님 강의에 나오길래 일단 적어봤어요)
  7. 프로세스 종료

 

✔️ Component Scan

  • Component Scan 은 @Component 주석이 붙어있는 클래스를 찾아 Bean 객체로 등록하는 과정입니다.
  • 결국 Component Scan 이란, 특정 객체를 Spring IOC Container 에서 관리하도록 객채를 생성하여 넣어줘~ 라는 과정입니다.
  • 그렇다면, 외부 라이브러리를 사용하여 생성하는 객체를 Bean 으로 등록하고 싶을때는 어떻게 해야할까요?
    👏 이럴 때 @Bean 어노테이션을 사용합니다.

구체적인 Bean 사용 방법에 대해서는 조금 더 아래에서 공부해보겠습니다.

 

✔️ Bean Scope

  • 기본적으로는 Singleton 입니다. 한번 빈 객체로 등록되면 객체는 재 생성 되지않고 생성된 객체를 주입받아 사용할 수 있습니다.
  • 프로토타입 : prototype scope 로 설정하면 의존성을 주입 받을 때마다 IOC Container 에서 객체를 생성합니다. 기본적으로 초기화까지 진행하고 주입된 객체에 대한 관리(할당 해제)는 관여하지 않습니다.
  • 그 외에 request, session, application, websocket 의 범위마다로 Bean 의 생명주기를 설정할 수 있습니다.

ScopeDescription (참고)

singleton (Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
prototype Scopes a single bean definition to any number of object instances.
request Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
session Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
application Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
websocket Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.

3. Bean 의 사용 이유

개인적인 생각 정리 입니다.

Spring Framework 에서 제공하는 IOC 컨테이너를 통한 의존성 주입을 위해 Bean 을 사용하는 목적이 가장 크다고 생각되긴 합니다.

여기서 조금 더 생각해보면

  • Bean 객체는 Default 로 Singleton Scope 를 가집니다. 
  • OkHttpClient 와 같은 외부 라이브러리 객체를 코드단에서 직접 생성하지 않고 Bean으로 등록하였을 때, 객체 인스턴스가 여러 개 생김으로 써 발생하는 다양한 문제점(메모리, 라이브러리 커넥셔 풀 등등,,)에 대해 고민할 필요가 없어지게됩니다.
  • (추가로, 의존성을 외부에서 주입하기 때문에 특정 라이브러리를 필요하는 기능을 인터페이스로 추출한다면 라이브러리 변경에도 자유로울 수 있겠네요)

즉, 메모리 관점에서 객체를 Singleton 으로 관리할 수 있다는 것 또한 Bean 의 주요한 장점이라고 생각됩니다.


4. Spring Bean 의 권장 사용 방법

⚙️ Config

  • Bean 으로 등록할 객체를 정의합니다.

✔️ Bean 등록과정

  • Bean 의 Default Scope 는 Singleton 이기 때문에 @Configuration이 붙어있는 클래스를 Component Scan 과정에서 탐색하고
  • @Bean이 붙어있는 메서드의 응답값을 IOC Contanier 에 등록합니다. (메서드는 1번만 실행)
  • 이때 IOC 컨테이너에 등록되는 Bean 객체의 이름은 메서드 이름입니다. (name 속성으로 지정 가능)
  • 만약 아래와 같이 같은 응답객체를 Bean 으로 등록하였을 때 @Primay 주석을 이용하여 Default Bean 객체를 지정할 수 있습니다.
@Configuration
public class OkHttpClientConfig {

    @Primary
    @Bean
    public OkHttpClient okHttpClient() {
        return new OkHttpClient.Builder()
            .connectTimeout(60, TimeUnit.SECONDS)
            .build();
    }

    @Bean
    public OkHttpClient okHttpClientLongConnection() {
        return new OkHttpClient.Builder()
            .connectTimeout(5, TimeUnit.MINUTES)
            .build();
    }
}

 

⚙️ Bean 주입

  • Bean 객체는 의존성 주입이 가능하기 때문에 원하는 객체를 정의합니다.
  • 만약 @Primary 가 붙은 Default Bean 이 아닌 속성의 객체를 주입하고 싶다면 @Qualifer 주석을 통해 주입할 Bean 의 이름을 지정할 수 있습니다.
@Service
@RequiredArgsConstructor
public class TestService {

    private final OkHttpClient okHttpClient;

    @Qualifier("okHttpClientLongConnection")
    private final OkHttpClient okHttpClientLongConnection;

    public void hello() throws IOException {
        var response = okHttpClient.newCall(null).execute();
        var response2 = okHttpClientLongConnection.newCall(null).execute();
    }
}

 

❗️ 만약 생성자 주입을 Lombok의 @RequiredArgsConstructor 사용한다면 아래와 같은 오류가 발생합니다. 이는 롬복에 @Qualifer 가 붙은 주석을 런타임시 실제 생성자로 변환할 때 함께 복사하지 못해서 나오는 에러입니다.

Lombok does not copy the annotation 'org.springframework.beans.factory.annotation.Qualifier' into the constructor

❗️아래와 같은 별도의 설정 파일(lombok.config) 을 통해 @Qualifier 어노테이션도 함께 복사하도록 세팅이 가능합니다.

# lombok.config
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier

 

각각 다른 객체가 주입된 결과


5. Spring Bean 의 다른 사용방법 (권장 x)

사실 아래와 같이도 Bean 을 사용할 수 있습니다.

  • @Configuration 또한 @Component 를 포함하기에 해당 클래스도 Bean 객체로 등록되어 있습니다.
  • 또한 @Bean 이 명시된 메서드의 응답값이 Bean 객체로 지정되어 있기 때문에 해당 메서드를 실행해도, 실제로 메서드가 실행되지 않으며 메서드 이름으로 Bean 객체를 찾아서 반환합니다. (싱글톤)
@Service
@RequiredArgsConstructor
public class TestService {

    private final OkHttpClientConfig okHttpClientConfig;

    public void hello2() {
        OkHttpClient okHttpClient1 = okHttpClientConfig.okHttpClient();
        OkHttpClient okHttpClient2 = okHttpClientConfig.okHttpClientLongConnection();
    }
}

 

다만, 이러한 방식은 Spring 에서 권장하는 방식이 아니라고 합니다.
@Configuration 은 @Bean 을 관리하는 목적으로만 사용하고 Bean 객체를 직접 주입받는게 싱글 톤을 명확하게 보장하는 방식이라고 하네요.

 

 

끝읕