본문 바로가기
[inflearn] 스프링 핵심 원리 - 기본편/섹션 9 - 빈 스코프

9-5. 요청할 때마다 새로운 프로토타입 빈 반환하기

by 슬픈 야옹이 2024. 7. 28.

 

요청할 때마다 새로운 프로토타입 빈을 제공하도록 하는 방법은 다음 몇 가지가 있다.

 

  • 스프링 컨테이너에 요청
    • 스프링 컨테이너를 의존해서 요청이 들어올 때마다 getBean()을 호출한다.
  • ObjectFactory, ObjectProvider (스프링 기능)
    • 지정한 빈을 컨테이너에서 대신 조회해주는 ObjectProvider를 사용한다.
    • 이전에는 ObjectFactory가 있었고, 현재는 편의기능이 추가된 ObjectProvider를 사용한다.
  • JSR-330 Provider (자바 표준)
    • JSR-330 자바 표준 Provider를 사용한다.
    • 스프링 말고 다른 환경에서도 사용할 수 있다는 특징이 있다.

 

하나씩 살펴보자.

 

전체 테스트에서 사용되는 PrototypeBean 클래스

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

    public void addCount() {
        this.count++;
    }

    public int getCount() {
        return this.count;
    }

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

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

}

 

 

 

스프링 컨테이너에 요청

가장 단순한 방법이다. ApplicationContext를 의존해 주입받고, 필요할 때마다 getBean()을 호출한다.

 

이런 방식을 의존관계를 직접 찾는다는 의미로 DL (Dependency Lookup, 의존관계 조회, 탐색)이라 한다.

 

ClientBean 클래스

@Scope("singleton")
static class ClientBean2 {

    private final ApplicationContext applicationContext;

    @Autowired
    public ClientBean2(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public int logic() {
        PrototypeBean prototypeBean = applicationContext.getBean(PrototypeBean.class);
        prototypeBean.addCount();
        int count = prototypeBean.getCount();
        return count;
    }

}

 

테스트코드

@Test
public void singletonClientUsePrototype2() {
    ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean2.class, PrototypeBean.class);

    ClientBean2 clientBean1 = ac.getBean(ClientBean2.class);
    int count1 = clientBean1.logic();
    assertThat(count1).isEqualTo(1);


    ClientBean2 clientBean2 = ac.getBean(ClientBean2.class);
    int count2 = clientBean2.logic();
    assertThat(count2).isEqualTo(1);
}

 

빈이 필요할 때마다 컨테이너에 직접 요청하므로 각각 다른 프로토타입 빈을 받아 사용할 수 있다.

 

문제는 해결했지만, 이렇게 작성한 코드는 스프링 컨테이너에 종속적이고 단위 테스트가 어려워진다.

 

 

 

ObjectFactory, ObjectProvider

스프링에 제공하는 ObjectProvider나 ObjectFactory를 사용한다.

 

ObjectProvider는 지정한 빈을 컨테이너에서 대신 찾아주는, 즉 DL 기능을 제공한다.

 

기존 ObjectFactory를 상속받아 편의기능을 추가한 것이 ObjectProvider이다.

 

ObjectProvider를 의존관계로 주입받아 사용한다.

// ObjectProvider를 이용한 프로토타입 빈 조회
@Scope("singleton")
static class ClientBean3 {
    private final ObjectProvider<PrototypeBean> prototypeBeans;

    @Autowired
    public ClientBean3(ObjectProvider<PrototypeBean> prototypeBeans) {
        this.prototypeBeans = prototypeBeans;
    }

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

}

 

 

테스트코드

@Test
public void singletonClientUsePrototype3() {
    ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean3.class, PrototypeBean.class);

    ClientBean3 clientBean1 = ac.getBean(ClientBean3.class);
    int count1 = clientBean1.logic();
    assertThat(count1).isEqualTo(1);


    ClientBean3 clientBean2 = ac.getBean(ClientBean3.class);
    int count2 = clientBean2.logic();
    assertThat(count2).isEqualTo(1);

}

 

 

특징

  • 스프링에 종속적이지만, 필요한 DL 기능만 편리하게 이용할 수 있다.

 

 

 

JSR-330 Provider

자바 표준인 javax.inject.Provider를 사용하는 방법이다.

 

gradle에 JSR-330 라이브러리를 추가한 뒤, Provider<T>를 의존한다.

 

gradle에 라이브러리 추가

 

Provider를 이용한 ClientBean

// 자바 표준 Provider를 이용한 프로토타입 빈 조회
@Scope("singleton")
static class ClientBean4 {
    private final Provider<PrototypeBean> prototypeBeans;

    @Autowired
    public ClientBean4(Provider<PrototypeBean> prototypeBeans) {
        this.prototypeBeans = prototypeBeans;
    }

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

}

 

테스트코드

@Test
public void singletonClientUsePrototype4() {
    ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean4.class, PrototypeBean.class);

    ClientBean4 clientBean1 = ac.getBean(ClientBean4.class);
    int count1 = clientBean1.logic();
    assertThat(count1).isEqualTo(1);


    ClientBean4 clientBean2 = ac.getBean(ClientBean4.class);
    int count2 = clientBean2.logic();
    assertThat(count2).isEqualTo(1);

}

 

Provider 객체의 get() 메서드를 호출하면 내부에서 스프링 컨테이너를 통해 해당 빈을 조회한다.

 

특징

  • 별도의 라이브러리가 필요하다.
  • 자바 표준이기 때문에 스프링 종속적이지 않다.
  • get() 메서드 하나로 기능이 단순하다.

 

 

+) ObjectProvider(스프링 제공)와 JSR-330 Provider 중 무엇을 써야 하는가?

 

드물게 스프링 외의 컨테이너에서 동작하도록 작성해야 하는 경우라면 JSR-330을 사용하고,

특별히 다른 컨테이너를 사용할 일이 없다면 ObjectProvider를 사용하는 편이 좋다. (편의기능이 더 많기 때문)