스프링

프로토타입 스코프 빈과 싱글톤 빈을 함께 사용시 문제점

salmon16 2023. 7. 25. 17:23

프로토타입 스코프 빈과 싱글톤 빈을 함께 사용하면 문제점이 발생할 수 있다.

아래 예시를 통해 알아보자.

 

위 그림에 프로토타입 스코프 빈은 count라는 0으로 초기화된 private 변수를 가지며 메서드 addCount()를 통해 count 값을 1 증가시킨다.

 

우리가 원하는 작동 방식은 클라이언트 A와 B가 각각의 프로토타입 스코프 빈을 할당받아 각각의 count값을 가지고 각자 addCount() 메서드를 통해서 1씩 증가시킨 count 1 값을 가지기를 원한다. 

 

하지만. cilentBean은 싱글톤이고 cilentBean은 프로토타입 스코프 빈을 생성자로 의존관계를 주입받는다.

그럼 주입 시점에 스프링 컨테이너에 프로토타입 빈을 요청한다.  그럼 스프링 컨테이너는 프로토타입 빈을 생성해서 clientBean에 반환해서 clientBean 내부에 프로토타입 빈을 가지게 된다.

 

여기서 문제가 발생한다 클라이언트 A와 B는 clientBean을 요청해서 받는데 clientBean은 싱글톤이므로 클라이언트 A와B는 같은 인스턴스의 clientBean을  받게 된다. 이러면 클라이언트 A와B 둘 다 clientBean 내부에 있는 프로토타입을 사용하게 되므로 같은 프로토타입 빈을 사용하게 돼서 각자 addCount() 메서드를 호출하면 count값이 2가 되어버린다.

 

이 문제를 해결하는 방법은 여러 가지가 있다.

  • 싱글톤 빈이 프로토타입을 사용할 때마다 스프링 컨테이너에 새로 요청하는 방법
  • ObjectProvider사용
  • JSR-330 Provider 사용

사용할 때 마다 스프링 컨테이너에 요청

싱글톤 빈이 프로토타입을 사용할 때마다 스프링 컨테이너에 새로 요청하는 방법이다.

이를 위해서는 스프링컨테이너를 주입받아야 한다.

static class ClientBean {
    @Autowired private ApplicationContext ac;
    public int logic() {
        PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
        prototypeBean.addCount();
        int count = prototypeBean.getCount();
        return count;
    }
}

위 코드와 같이 스프링컨테이너를 주입 받아 logic() 메서드를 클라이언트가 요청할 때마다 빈을 얻어오는 방법이다.

이렇게 의존관계를 외부에서 주입받는 게 아니라 의존관계를 찾는 것을 Dependency Lookup(DL)이라고 할 수 있다.

 

ObjectProvider사용

위 방법에서 DL을 사용했는데 DL을 제공하는 서비스를 가진 ObjectProvider이 있다.

먼저 코드를 보자.

static class ClientBean {
    @Autowired
    private ObjectProvider<PrototypeBean> prototypeBeanProvider;

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

ObjectProvider은 getObject() 메서드를 통해 새로운 프로토타입 빈을 생성해 준다. 이 메서드를 호출하면 스프링 컨테이너에서 빈을 찾아서 반환한다. 

ObjectProvider은 스프링 컨테이너에서 주입해 준다.

ObjectProvider<사용 타입 클래스> 이렇게 사용하면 된다.

JSR-330 Provider 사용

자바 표준을 사용하는 방법이다.

이 방법을 사용하려면 아래를 gradle에 dependencies안에 추가를 해주어야 한다.

implementation 'javax.inject:javax.inject:1'

사용은 아주 간단하다.

javax.inject을 선택해주면 된다.

그리고 여기선 DL은 get()메서드를 사용하기 때문에 get() 메서드를 호출해 주면 된다.

static class ClientBean {
    @Autowired
    private Provider<PrototypeBean> prototypeBeanProvider;

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

자바 표준이기 때문 단위 테스트 코드를 짜기 쉬워진다.

 

출처 : 인프런 스프링 핵심 원리 - 기본 편  김영한