[Spring - Inflearn] Spring Bean Scope - 스프링 빈 스코프

728x90
반응형
SMALL

Bean Scope

스프링은 빈이라는 개념으로 객체를 만들고 관리해준다.

 

스프링 빈은 기본적으로 싱글톤 스코프로 생성되기에, 스프링 컨테이너의 시작과 함께 생성되어 스프링 컨테이너가 종료될 때까지 유지된다고 알고 있다.

 

이렇게, 스프링 빈이 존재할 수 있는 범위빈 스코프라고 한다.

 

Bean Scope종류

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

 

Singleton Scope

public class SingletonTest {

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

        SingletonBean bean1 = ac.getBean(SingletonBean.class);
        SingletonBean bean2 = ac.getBean(SingletonBean.class);

        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
SingletonBean.destroy

 

 

prototype scope

public class PrototypeTest {

    @Test
    public void prototypeBeanFind() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);

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

        assertThat(bean1).isNotSameAs(bean2);

        bean1.destroy();
        bean2.destroy();
    }

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

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

 

프로토타입 스코프의 빈을 스프링 컨테이너에 요청하면, 그 시점에 스프링 컨테이너는 프로토타입 빈을 생성하고 필요한 의존관계를 주입하여 클라이언트에게 반환한다.

 

이후, 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환한다.

 

프로토타입 스코프 결과 :

find prototypeBean 1
PrototypeBean.init

find prototypeBean 2
PrototypeBean.init

PrototypeBean.destroy
PrototypeBean.destroy

프로토타입 스코프인 경우, 스프링 컨테이너는 프로토타입 빈 생성, 의존관계 주입, 초기화까지만 관여한다.

 

클라이언트에 빈을 반환한 이후에는 프로토타입 빈을 관리하지 않는다.

 

그렇기에, ac.close()를 호출해도 @PreDestroy 종료 메서드가 호출되지 않는다.

 

클라이언트가 직접 bean1.destroy()를 호출해야 종료가 된다.

 

 

Singleton Scope && Prototype Scope

스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 된다.

 

하지만 싱글톤 빈은 생성 시점에만 의존관계 주입을 받기 때문에, 프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과 함께 계속 유지되는 것이 문제이다.

public class SingletonWithPrototype {

    @Test
    public void singletonCluentUsePrototype() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
        ClientBean bean1 = ac.getBean(ClientBean.class);
        int count1 = bean1.logic();
        assertThat(count1).isEqualTo(1);

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

    @Scope
    @RequiredArgsConstructor
    static class ClientBean {

		private final Prototype prototypeBean;

        public int logic() {
            prototypeBean.add();
            return prototypeBean.getCount();
        }
    }

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

        public void add() {
            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");
        }
    }
}

 

위처럼, 싱글톤 빈과 함께 유지되기에 count가 증가하는 것을 볼 수 있다. 

 

즉, 개발자는 프로토타입 빈이 계속 유지 되는 것이 아니라 새로 생성되는 것을 의도했지만, 그렇게 되지 않는다.

 

이를 해결하기 위해 Provider를 사용한다.

 

public class SingletonWithPrototype {

    @Test
    public void prototypeFind() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);

        PrototypeBean bean1 = ac.getBean(PrototypeBean.class);
        bean1.add();
        assertThat(bean1.getCount()).isEqualTo(1);

        PrototypeBean bean2 = ac.getBean(PrototypeBean.class);
        bean2.add();
        assertThat(bean2.getCount()).isEqualTo(1);
    }

    @Test
    public void singletonCluentUsePrototype() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
        ClientBean bean1 = ac.getBean(ClientBean.class);
        int count1 = bean1.logic();
        assertThat(count1).isEqualTo(1);

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

    @Scope
    @RequiredArgsConstructor
    static class ClientBean {

//        private final ObjectProvider<PrototypeBean> prototypeBeanProvider;
        private final Provider<PrototypeBean> prototypeBeanProvider;

        public int logic() {
//            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            PrototypeBean prototypeBean = prototypeBeanProvider.get();
            prototypeBean.add();
            return prototypeBean.getCount();
        }
    }

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

        public void add() {
            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");
        }
    }
}

prototypeBeanProvider.getObject() 혹은 prototypeBeanProvider.get() 처럼 의존관계를 외부에서 주입(DI) 받는 것이 아니라, 필요한 의존관계를 직접 찾게 하는 것을 Dependency Lookup(DL) 의존관계 조회(탐색)이라 한다. 

 

이 정도만 알아두자.

 

실무에서 이런 경우는 매우 드물다.

 

 

 

728x90
반응형
LIST