Post

Spring 컨테이너와 빈 (1)

Intro

Spring 컨테이너와 빈에 대해 알아봅시다.

✅ Spring 컨테이너

  • 스프링 컨테이너는 스프링 어플리케이션 내에서 자바 객체를 관리하는 공간이며 IoC 개념을 구현한 도구 입니다
  • 스프링 컨테이너에 관리하는 자바 객체를 스프링에서는 빈(Bean)이라고 표현합니다.
  • 스프링에서 제공하는 실제 구현체로서, 빈의 생성, 의존성 주입, 생명 주기 관리 등을 담당합니다.
  • 어플리케이션 동작 중 빈이 필요한 시점에 개발자 대신 DI를 해줍니다.
  • BeanFactory 인터페이스와 그의 구현체인 ApplicationContext의 구조로 이루어져 있습니다.
  • BeanFactory은 지연로딩, ApplicationContext은 즉시로딩 전략을 사용합니다. 즉시로딩은 애플리케이션 실행 시점에 모든 싱글톤 빈을 로드합니다. 반연에 지연로딩은 필요한 시점에 로드하기 때문에 런타임 시점에서 지연시간이 발생할 수 있습니다.
  • 이 외에 ApplicationContextBeanFactory 보다 더 많은 기능을 제공하므로 Spring 공식문서에서는 ApplicationContext사용을 권장하고 있습니다.

✅ Spring Bean

스프링에서 빈(Bean)은 스프링 컨테이너에 의해 관리되는 자바 객체를 의미합니다. 애플리케이션에서 필요한 객체들을 스프링 컨테이너에 등록하고, 이 객체들이 필요할 때 스프링이 자동으로 의존성을 주입해줍니다. 빈은 스프링의 IoC(Inversion of Control) 원칙에 따라 생성되고 관리됩니다.

  • 스프링 컨테이너는 어플리케이션이 시작될ㄷ 때 지정된 빈 설정을 참고하여 빈을 생성하고, 이 빈들 간의 의존성을 자동으로 주입(DI) 합니다.
  • @Component, @Service, @Repository, @Controller 등의 애너테이션을 사용하거나, 자바 설정 파일에서 @Bean 애너테이션을 사용해 직접 빈을 정의할 수 있습니다.

✅ Spring Bean Scope

스프링 빈 스코프는 싱글톤프로토타입으로 나뉘며 이에 따라 빈의 생명 주기도 달라지게 됩니다. 디폴트는 싱글톤으로 설정 되어 있습니다.

싱글톤

디폴트 스코프, 애플리케이션 전체에서 하나의 슨트런스만 생성됩니다. 컨테이너가 처음 빈을 생성하면 그 이후로는 동일한 객체를 계속해서 재사용합니다.

  • 메모리 효율적이고, 객체를 공유하는 방식입니다.
  • 애플리케이션 전반에서 동일한 상태를 유지하는 빈이 필요할 때 유용합니다.
  • 일반적인 서비스 레이어나 데이터 접근 객체(DAO)는 싱글톤으로 사용하는 것이 좋습니다.
  • 예시
    1
    2
    3
    4
    
    // 한번만 생성 됨
    @Service
    public class MyService {
    }
    

프로토타입

프로토타입 스코프는 빈을 요청할 때마다 새로운 인스턴스를 생성하는 방식입니다. 매번 다른 객체를 반환합니다.

  • 매번 새로운 객체가 필요할 때 유용하지만, 메모리 사용량이 증가할 수 있습니다.
  • 빈의 상태가 호출될 때마다 달라지길 원할 때 사용합니다.
  • 요청마다 새로운 상태를 유지해야 하는 객체나, 상태를 공유하면 안되는 경우
  • 예시
    1
    2
    3
    4
    5
    6
    
    // 요청할 때마다 새로운 객체가 생성됨
    @Component
    @Scope("prototype")
    public class MyPrototypeBean {
        
    }
    

📋 빈 스코프 사용 예제

싱글톤, 프로토타입 각각 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class BeanTest {

  @Test
  @DisplayName("빈 스코프 테스트")
  void 빈스코프테스트() throws Exception {
    //given
    System.out.println("======= 싱글톤빈 인스턴스는 context가 열리는 순간 생성됨 =======");
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SingletonCounter.class, PrototypeCounter.class);
    System.out.println("======================================================");

    System.out.println("======= 프로토타입빈 인스턴스는 빈을 호출하는 시점에 생성됨 =======");
    final SingletonCounter singletonCounter1 = context.getBean(SingletonCounter.class);
    final SingletonCounter singletonCounter2 = context.getBean(SingletonCounter.class);
    final PrototypeCounter prototypeCounter1 = context.getBean(PrototypeCounter.class);
    final PrototypeCounter prototypeCounter2 = context.getBean(PrototypeCounter.class);
    System.out.println("======================================================");
    //when

    singletonCounter1.addCount();
    singletonCounter2.addCount();
    prototypeCounter1.addCount();
    prototypeCounter2.addCount();

    final int singletonCount = singletonCounter2.getCount();
    final int prototypeCount = prototypeCounter2.getCount();

    System.out.println("====== 싱글톤빈은 사용이 끝나는 순간 소멸됨 ============");
    context.close();
    System.out.println("===============================================");

    System.out.println("====== 프로토타입빈은 직접 소멸해줘야함 ======");
    prototypeCounter1.destroy();
    prototypeCounter2.destroy();
    System.out.println("======================================");


    //then
    assertAll(
      () -> assertThat(singletonCounter1).isEqualTo(singletonCounter2),
      () -> assertThat(prototypeCounter1).isNotEqualTo(prototypeCounter2),
      () -> assertThat(singletonCount).isEqualTo(2),
      () -> assertThat(prototypeCount).isEqualTo(1)
    );


  }

  // 기본이 싱글톤
  static class SingletonCounter {

    private int count = 0;

    public void addCount() {
      count++;
    }

    public int getCount() {
      return count;
    }

    @PostConstruct
    public void init() {
      System.out.println("Singleton.init " + this);
    }
    @PreDestroy
    public void destroy() {
      System.out.println("Singleton.destroy");
    }
  }

  @Scope("prototype")
  static class PrototypeCounter {
    private int count = 0;

    public void addCount() {
      count++;
    }

    public int getCount() {
      return count;
    }

    @PostConstruct
    public void init() {
      System.out.println("Prototype.init " + this);
    }
    @PreDestroy
    public void destroy() {
      System.out.println("Prototype.destroy");
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
======= 싱글톤빈 인스턴스는 context가 열리는 순간 생성됨 =======
Singleton.init com.example.springstudy_2024_06_02.BeanTest$SingletonCounter@db56ff78
======================================================
======= 프로토타입빈 인스턴스는 빈을 호출하는 시점에 생성됨 =======
Prototype.init com.example.springstudy_2024_06_02.BeanTest$PrototypeCounter@954f6a5b
Prototype.init com.example.springstudy_2024_06_02.BeanTest$PrototypeCounter@6459a607
======================================================
====== 싱글톤빈은 사용이 끝나는 순간 소멸됨 ============
Singleton.destroy
===============================================
====== 프로토타입빈은 직접 소멸해줘야함 ======
Prototype.destroy
Prototype.destroy
======================================

img.png

  • 싱글톤빈은 스프링 컨텍스트가 로드되는 시점에 생성됩니다.
  • 프로토타입빈은 해당 빈이 호출되는 시점에 생성됩니다.
  • 싱글톤빈은 1개의 인스턴스만 생성됩니다. 따라서 모든 스레드에서 공유를 하게 되므로 필드에 상태를 갖는 변수를 두는것을 주의해야합니다.
  • 프로토타입빈은 호출 될 때마다 빈을 생성합니다.
  • 싱글톤빈은 사용이 종료되는 시점(컨텍스트 종료)에 소멸이 됩니다. (컨텍스트 종료 != 애플리케이션 종료)
  • 프로토타입빈은 사용이 종료되는 시점에 소멸되지 않으므로 개발자가 직접 소멸시켜줘야 합니다.
  • 싱글톤빈은 1개의 인스턴스이므로 singletonCounter1singletonCounter2는 동일한 인스턴스 입니다. 따라서 동일한 count값을 참조하므로 각 인스턴스가 addCount()를 호출하면 count는 2번 증가하게 됩니다.
  • 프로토타입빈은 서로 다른 인스턴스이므로 prototypeCounter1prototypeCounter2는 각각 다른 count를 참조하게 됩니다. 따라서 각 인스턴스가 addCount()를 호출하면 서로 다른 count가 1번씩 증가하게 됩니다.

Reference

스프링 핵심 원리 - 기본편

Continue

싱글톤빈과 프로토타입빈을 함께 사용하는 예제

Spring 컨테이너와 빈 (2)

This post is licensed under CC BY 4.0 by the author.