본문 바로가기
스터디/Spring

[김영한님 스프링 핵심 원리] 빈 스코프

by Big Sun 2022. 11. 3.
728x90

 

스프링에서 지원하는 스코프

  • 싱글톤 : 기본스코프로 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.
  • 프로토 타입 : 스프링 컨테이넌느 프로토타입 빈의 생성과 의존관계 주입가지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다.

 

싱글톤

public class SingletonTest {

    @Test
    void singletonBeanFind(){

        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);
        System.out.println("초기화 끝");
        SingletonBean bean1 = ac.getBean(SingletonBean.class);
        SingletonBean bean2 = ac.getBean(SingletonBean.class);

        System.out.println("bean1 = " + bean1);
        System.out.println("bean2 = " + bean2);

        assertThat(bean1).isSameAs(bean2);
        ac.close();
    }

    @Scope("singleton")
    static class SingletonBean {

        @PostConstruct
        public void init(){
            System.out.println("SingletonBean.init");
        }

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

}
SingletonBean.init
초기화 끝
bean1 = hello.core.scope.SingletonTest$SingletonBean@3cdf2c61
bean2 = hello.core.scope.SingletonTest$SingletonBean@3cdf2c61
22:06:42.226 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@1b11171f, started on Wed Nov 02 22:06:41 KST 2022
SingletonBean.destroy

 

싱글톤 빈이 생성된 후에 초기화 메서드가 진행된 후에, 싱글톤 빈을 조회하는 것을 알 수 있다

그리고, 조회한 빈들 모두 같은 인스턴스인 것을 알 수 있다.

 

프로토 타입

 

		@Test
    void prototypeTest(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
        System.out.println("find prototypeBean1");
        PrototypeBean bean1 = ac.getBean(PrototypeBean.class);
        System.out.println("find prototypeBean2");
        PrototypeBean bean2 = ac.getBean(PrototypeBean.class);

        System.out.println("bean1 = " + bean1);
        System.out.println("bean2 = " + bean2);

        Assertions.assertThat(bean1).isNotSameAs(bean2);

        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");
        }
    }
find prototypeBean1
PrototypeBean.init
find prototypeBean2
PrototypeBean.init
bean1 = hello.core.scope.PrototypeTest$PrototypeBean@3cdf2c61
bean2 = hello.core.scope.PrototypeTest$PrototypeBean@13ad5cd3

 

프로토타입 스코프의 빈은 스프링 컨테이너에 빈을 요청할 때 생성되고, 초기화 메서드도 실행된다.

또한, 생선된 빈들은 모두 다르고, 프로토타입이므로 종료 메서드는 실행되지 않는다.

 

 

그렇다면, 싱글톤 빈과 프로토타입빈을 함께 사용한다면, 어떻게 될까??

 

 

싱글톤 빈 + 프로토타입 빈

@Test
    void singletonClientUsePrototype() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class, ClientBean.class);

        ClientBean bean1 = ac.getBean(ClientBean.class);
        int count1 = bean1.logic();
        Assertions.assertThat(count1).isEqualTo(1);


        ClientBean bean2 = ac.getBean(ClientBean.class);
        int count2 = bean1.logic();
        Assertions.assertThat(count2).isEqualTo(2);
    }

    static class ClientBean {

        private final PrototypeBean prototypeBean;

        @Autowired
        public ClientBean(PrototypeBean prototypeBean) {
            this.prototypeBean = prototypeBean;
        }

        public int logic() {
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }
    }

@Scope("prototype")
    static class PrototypeBean {

        private int count = 0;

        void addCount(){
            count++;
        }

        public int getCount(){
            return count;
        }

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

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

    }

테스트를 해보면 테스트가 성공한다.

이 말은 곧 bean1, bean2의 PrototypeBean 필드의 인스턴스가 같다는 말이다.

다시말하자면, 싱글톤 빈을 요청할 때 이미 프로토타입 클래스가 의존관계 주입이 되어서, 후에 다시 싱글톤 빈을 요청할 때 이미 프로토타입 클래스가 의존관계 주입이 되어있기 때문에 다시 프로토타입 의존관계 주입을 해주지 않았다는 말이다.

이는 프로토타입의 특성을 살리지 못하는 문제가 있다.

 

그렇다면, 싱글톤 빈과 프로토타입 빈을 함께 사용할 때, 어떻게 하면 사용할 때 마다 항상 새로운 프로토타입 빈을
생성할 수 있을까?

 

ObjectProvider라는 인터페이스 사용

@Test
void singletonClientUsePrototype() {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class, ClientBean.class);

    ClientBean bean1 = ac.getBean(ClientBean.class);
    int count1 = bean1.logic();
    Assertions.assertThat(count1).isEqualTo(1);


    ClientBean bean2 = ac.getBean(ClientBean.class);
    int count2 = bean1.logic();
    Assertions.assertThat(count2).isEqualTo(1);
}

static class ClientBean {

    private final ObjectProvider<PrototypeBean> prototypeBeanProvider;

    ClientBean(ObjectProvider<PrototypeBean> prototypeBeanProvider) {
        this.prototypeBeanProvider = prototypeBeanProvider;
    }

    public int logic() {
        PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
        prototypeBean.addCount();
        int count = prototypeBean.getCount();
        return count;
    }
}

 

ObjectProvider<T> 인터페이스를 사용하면, prototypeBeanProvider.getObject()를 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다. -> 내부에서는 스프링 컨테이너를 통해서 해당 빈을 찾아서 반환해준다.

 

이 방법은 간단하고 별도의 라이브러리가 필요 없고, 상속, 옵션 등 편의기능이 있는 대신에 스프링에 의존적이다.

 

이상입니다!~

728x90