빈 스코프 - 싱글톤과 프로토타입

해당 내용은 '스프링 입문을 위한 자바 객체 지향의 원리와 이해'와 인프런 김영한님의 '스프링 핵심 원리 - 기본편' 강의를 참고하였습니다.


빈 스코프?

스프링 빈은 스프링 컨테이너의 시작과 함께 생성되고, 스프링 컨테이너가 종료될 때까지 유지된다.

이와 같이 스프링 빈이 존재할 수 있는 범위를 의미하는 것을 우리는 스코프 라고 한다.

스프링은 다음과 같은 스코프를 지원한다.

  • 싱글톤 : 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.
  • 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다.
  • 웹 관련 스코프
    • request : 웹 요청이 들어오고 나갈때까지 유지되는 스코프
    • session : 웹 세션이 생성되고 종료될 때까지 유지되는 스코프
    • application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

우리가 여태까지 사용한 빈들은 전부 싱글톤 스코프이니, 굳이 싱글톤 스코프의 사용법에 대해서는 다루지 않고, 바로 프로토타입 스코프부터 알아보자.

프로토타입의 빈 요청

보통 외부 클라이언트가 싱글톤 빈을 요청하면 스프링 컨테이너에서는 하나의 싱글톤 빈만 생성하고 같은 요청에 대해 같은 객체의 인스턴스로 스프링 빈을 반환하지만,

보통 우리가 아는 싱글톤 스코프 타입의 빈은 다음과 같이 스프링 컨테이너에 의해 관리된다.

  1. 외부에서 싱글톤 스코프의 빈을 스프링 컨테이너에 요청
  2. 스프링 컨테이너는 해당 싱글톤 스코프 빈을 생성(빈 생성, DI, 초기화)하고 반환
  3. 이 싱글톤 스코프 빈은 스프링 컨테이너에 의해 관리
  4. 이후의 동일한 요청에는 동일한 싱글톤 스코프 빈으로 반환

프로토 타입의 빈 요청은 다르다.

  1. 외부에서 프로토 타입의 빈을 스프링 컨테이너에 요청
  2. 스프링 컨테이너는 해당 프로토 타입의 빈을 생성(빈 생성, DI, 초기화)하고 반환
  3. 이 프로토 타입 빈은 컨테이너에 의해 관리되지 않음.
  4. 이후의 같은 요청이 오면, 그때마다 새로운 프로토 타입의 빈을 생성해서 반환

자 여기서 중요한건 프로토타입 빈은 스프링 컨테이너에서 빈 생성, DI, 초기화까지만 하고 반환하며, 그 뒤로는 관리하지 않는다는 것이다.

프로토타입 빈을 관리할 책임은 해당 빈을 요청한 클라이언트에게 있다.

그렇기에 우리가 이전에 공부했던 @PreDestroy와 같은 종료 메서드가 호출되지 않는다.

그럼 이제 코드로 알아보자.

우선 싱글톤 빈부터 확인해보자.

SingletonTest.java

public class SingletonTest {

    @Test
    void singletonBeanFine(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);

        SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
        SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
        System.out.println("singletonBean1 = " + singletonBean1);
        System.out.println("singletonBean2 = " + singletonBean2);
        Assertions.assertThat(singletonBean1).isSameAs(singletonBean2);

        ac.close();
    }

    @Scope("singleton") // default 라서 사실 안해도됨..
    static class SingletonBean{
        @PostConstruct
        public void init(){
            System.out.println("SingletonBean.init");
        }

        @PreDestroy
        public void destroy(){
            System.out.println("SingletonBean.destroy");
        }
    }
}

테스트를 돌리면?

SingletonBean.init
singletonBean1 = hello.core.scope.SingletonTest$SingletonBean@71a6dcb7
singletonBean2 = hello.core.scope.SingletonTest$SingletonBean@71a6dcb7
SingletonBean.destroy

init 과 destroy가 모두 호출되었고, 두 번의 요청에 같은 빈을 반환한 것을 알 수 있다.

이번에는 프로토타입 빈 스코프를 사용해보자.

PrototypeTest.java

import static org.assertj.core.api.Assertions.assertThat;

public class PrototypeTest {

    @Test
    void PrototypeBeanFind(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
        System.out.println("find prototypeBean 1");
        PrototypeBean PrototypeBean1 = ac.getBean(PrototypeBean.class);

        System.out.println("find prototypeBean 2");
        PrototypeBean PrototypeBean2 = ac.getBean(PrototypeBean.class);

        System.out.println("PrototypeBean1 = " + PrototypeBean1);
        System.out.println("PrototypeBean2 = " + PrototypeBean2);

        assertThat(PrototypeBean1).isNotSameAs(PrototypeBean2);

        ac.close();
    }

    @Scope("prototype")
    static class PrototypeBean {
        @PostConstruct
        public void init(){
            System.out.println("PrototypeBean.init");
        }

        @PreDestroy
        public void destroy(){
            System.out.println("PrototypeBean.destroy");
        }
    }
}

이번에는 두 반환 빈이 다를 것이니 isNotSameAs를 사용했다.

결과는 다음과 같다.

find prototypeBean 1
PrototypeBean.init
find prototypeBean 2
PrototypeBean.init
PrototypeBean1 = hello.core.scope.PrototypeTest$PrototypeBean@2b7eccc4
PrototypeBean2 = hello.core.scope.PrototypeTest$PrototypeBean@f83d00bf

자 init이 2번 호출되었고, destroy는 단 한번도 호출되지 않았으며,

생성된 2개의 프로토타입 빈의 객체가 다른 것을 알 수 있다.

좋은 웹페이지 즐겨찾기