Spring - Prototype Bean Scope

HYK·2022년 5월 16일
0

spring

목록 보기
4/4

Bean Scope

  • 빈이 존재할 수 있는 범위를 나타낸다.

Spring에서 자주 사용되는 Bean Scope

  • Singleton : Spring Container에서 사용되는 가장 기본 Scope로 Container시작과 부터 종료시점까지 유지된다
  • Prototype : Spring Container가 빈의 생성과 주입만 담당하는 Scope 그이후 부터는 클라이언트가 관리 하게 된다(Singleton과는 반대로 호출시에 항상 다른 인스턴스가 필요할때 사용한다.)
  • Request : 사용자의 Request요청이 들어오고 Response 될때까지 유지되는 Scope
  • Session : Session이 생성되고 종료될 때까지의 Scope

Scope 지정방법

  • bean등록시에 Scope 애노테이션을 이용한다.
  • 기본 Scope는 singleton이고 이외에 옵션을 이용하면 다양한 Scope를 사용할 수 있다.

수동주입시

@Configuration
public class Config {

    @Bean
    @Scope("prototype")
    TestBean testBean(){
        return new TestBean();
    }
}

자동주입시

@Component
@Scope("prototype")
public class TestBean {
		...
}

Prototype Scope 사용시 주의할 점

Prototype Scope Bean을 Singleton Scope Bean이 주입받아서 사용할 때 Prototype은 Container가 Prototype Bean를 Singleton Bean를 호출시 마다 계속 새로 주입 해주는 것이 아니라 처음에 Singleton bean을 생성할 때 final로 생성해 주었던 Prototype Bean이 실행된다. 따라서 생성과 주입 까지는 Container가 관리하나 이후는 Client인 Singleton Bean 이 관리하기 되므로 Singleton Bean에 주입된 Prototype Bean은 마치 Singleton Bean처럼 사용된다.

문제점

  • 우리의 의도는 매번 로직을 실행할 때 마다 새로운 Prototype Bean을 원했지만 실제는 같은 Prototype Bean이 사용된다는 것이다.

  • Test : Singleton Bean에 Prototype Bean을 주입하고 Singleton Bean에서 logic을 실행할 때마다 Prototype Bean이 새로 생성되는지에 대한 테스트 (logic 메서드는 1을 카운트하는 Prototype Bean의 addCount 메서드를 호출하는 것)
public class SingletonWithPrototype {

    @Test
    public void SingletonClientUsePrototype() throws Exception {
        //given
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
        //when
        ClientBean bean1 = ac.getBean(ClientBean.class);
        ClientBean bean2 = ac.getBean(ClientBean.class);
        //then
        assertThat(bean1.logic()).isEqualTo(1);
        assertThat(bean2.logic()).isEqualTo(2);
        assertThat(bean1.prototype).isSameAs(bean2.prototype);
        //PreDestroy 확인용
        ac.close();
    }
       /**
        * 테스트 결과 : 성공
        * 우리가 원하는 의도는 bean1의 prototype과 bean2의 prototype과 서로 다른 인스턴스여야 하지만
        * 이 테스트에서는 같은 인스턴스로 확인 되어 우리가 원하는 결과와는 다름.
        * */

    @Scope("singleton")
    @RequiredArgsConstructor
    static class ClientBean {
        private final PrototypeBean prototype;

        public int logic() {
            prototype.addCount();
            return prototype.getCount();
        }
    }

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

        public int getCount() {
            return count;
        }

        public void addCount() {
            count++;
        }

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

        }

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

        }
    }
}
  • 어떻게 해야 해결할 수 있을까 ?
    ApplicationContext를 이용해서 logic 메서드를 호출할 때마다 새로운 Bean을 주입받는방법? 해결할 수 는 있지만 ApplicationContext에 의존해서 계속 Bean을 생성받는 방법은 그리 좋은 방법 같아 보이진 않는다. 다른 방법은?
	  private final ApplicationContext ac;
            
      public int logic() {
            PrototypeBean prototype = ac.getBean(PrototypeBean.class);
            prototype.addCount();
            return prototype.getCount();
      }

Provider를 사용하자!

  • 이렇게 DI 하는 방식이아닌 필요한 Bean을 탐색하는것을 DL(Dependency Lookup)우리말로 하면 의존관계 조회 정도로 이해하면 될것같다.
  • 어떻게하면 효율적으로 DL을 할 수 있을까? Provider를 이용해 필요한 타입의 Bean을 DL 해보자

public class SingletonWithPrototype {

    @Test
    public void SingletonClientUsePrototypeFind() throws Exception {
        //given
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
        //when
        ClientBean bean1 = ac.getBean(ClientBean.class);
        ClientBean bean2 = ac.getBean(ClientBean.class);
        //then

        assertThat(bean1.logic()).isEqualTo(1);
        assertThat(bean2.logic()).isEqualTo(1);

    }
    
    	/**
        * 테스트 결과 : 성공
        * 우리가 원하는 의도대로 서로 다른 Prototype Bean을 가지고 logic을 실행한 것을 볼 수 있다.
        * */

    @Scope("singleton")
    @RequiredArgsConstructor
    static class ClientBean {
        /**
         * ObjectProvider 스프링에 의존적
         * DL 기능을 해주는 객체 getObject호출시 원하는 bean을 찾아줌
         */
        private final ObjectProvider<PrototypeBean> beanProvider;

        public int logic() {
            PrototypeBean prototype = beanProvider.getObject();
            prototype.addCount();
            return prototype.getCount();
        }
    }

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

        public int getCount() {
            return count;
        }

        public void addCount() {
            count++;
        }

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

        }

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

        }
    }
}
  • 여기서 사용한 private final ObjectProvider<PrototypeBean> beanProvider; ObjectProvider는 ObjectFactory의 Stream,Optinal등 여러가지 편의 기능들을 추가한 인터페이스로 DL 기능을 보다 쉽고 편하게 해준다.
  • 자바 표준인 javax.inject.Provider 를 사용해도 괜찮다.

출처 : 스프링 핵심 원리

profile
Test로 학습 하기

0개의 댓글