본문 바로가기
Spring/spring 원리

프로토타입 빈을 싱글톤 빈과 사용시 문제점과 해결방안

by 킹차니 2021. 6. 22.
김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.
https://www.inflearn.com/courses?s=%EA%B9%80%EC%98%81%ED%95%9C

 

 

프로토타입 빈은 조회요청을 할 때 마다 새로운 빈이 생성되어 반환되고,

싱글톤 빈은 스프링 컨테이너가 올라간 뒤 생성되어 더 이상 생성되지 않고 컨테이너에 하나만 존재하게 된다.

그래서 여러 클라이언트가 프로토타입을 요청하면 각 클라이언트마다 다른 프로토타입을 가지게 된다. 하지만 항상 이렇게 되진 않는다. 

싱글톤 빈과 프로토타입 빈을 함께 사용할 때 문제가 되는데, 어떤 문제가 생기는지와 해결법을 알아보자.

 

프로토타입 빈

아래는 프로토타입 빈만을 사용하고 있는 상황이다.

  1. 클라이언트A는 스프링 컨테이너에 프로토타입 빈을 요청한다.
  2. 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x01)한다. 해당 빈의 count 필드 값은 0이다.
  3. 클라이언트는 조회한 프로토타입 빈에 addCount() 를 호출하면서 count 필드를 +1 한다.
  4. 결과적으로 프로토타입 빈(x01)의 count는 1이 된다.

4. 클라이언트B는 스프링 컨테이너에 프로토타입 빈을 요청한다.
5.
스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x02)한다. 해당 빈의 count 필드 값은 0이다.

6. 클라이언트는 조회한 프로토타입 빈에 addCount() 를 호출하면서 count 필드를 +1 한다.

7. 결과적으로 프로토타입 빈(x02)count1이 된다.

 

위 상황의 코드는 다음과 같다.

테스트는 물론 통과하고

init메소드로 인한 출력 결과는 다음과 같다.

당연히 다른 객체가 출력되는 것을 볼 수 있다.

 

하지만 싱글톤 빈이 프로토타입 빈을 사용하는 경우라면 달라진다.

 

문제점

싱글톤 빈이 프로토타입 빈을 의존관계로 주입받는 경우

1. clientBean싱글톤이므로, 보통 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 주입도 발생한다.
2. clientBean은 의존관계 자동 주입을 사용한다. 주입 시점에 스프링 컨테이너에 프로토타입 빈을 요청한다.

3. 스프링 컨테이너는 프로토타입 빈을 생성해서 clientBean 에 반환한다. 프로토타입 빈의 count 필드 값은 0이다.
4. 이제 clientBean은 프로토타입 빈을 내부 필드에 보관한다. (정확히는 참조값을 보관한다.)

 

5. 클라이언트 AclientBean 을 스프링 컨테이너에 요청해서 받는다. 싱글톤이므로 항상 같은 clientBean 이 반환된다.
6.
클라이언트 AclientBean.logic() 을 호출한다.
7.
clientBean의 logic()은 prototypeBeanaddCount() 를 호출해서 프로토타입 빈의 count를 증가시킨다. count값이 1이 된다.

 

8. 클라이언트 BclientBean 을 스프링 컨테이너에 요청해서 받는다.싱글톤이므로 항상 같은 clientBean 이 반환된다.
9. clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이다. 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성이 된 것이지, 사용 할 때마다 새로 생성되는 것이 아니다!
10. 클라이언트 BclientBean.logic() 을 호출한다.
11.
clientBean prototypeBeanaddCount() 를 호출해서 프로토타입 빈의 count를 증가한다. 원래 count 값이 1이었으므로 2가 된다.(즉 프로토타입 빈이지만 다른 빈이 아닌 클라이언트 빈(싱글톤)에 있는 같은 프로토타입을 사용한다.)

 

현 상황의 코드는 다음과 같다.

출력결과를 보면 스프링 컨테이너 생성시점에 의존관계 주입을 위해 생성된 프로토타입 빈은 당시에만 한번 생성되는 것을 볼 수 있고, 

테스트가 통과한것으로 보아 같은 프로토타입 빈의 count를 증가시켜 현재 count == 2인 것을 알 수 있다.

스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 된다. 그런데 싱글톤 빈은 생성 시점에만 의존관계 주입을 받기 때문에, 프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과 함께 계속 유지되는 것이 문제다.

아마 원하는 것이 이런 것은 아닐 것이다. 프로토타입 빈을 주입 시점에만 새로 생성하는게 아니라, 사용할 때 마다 새로 생성해서 사용하는 것을 원할 것이다.

참고: 여러 빈에서 같은 프로토타입 빈을 주입 받으면, 주입 받는 시점에 각각 새로운 프로토타입 빈이 생성 된다. 예를 들어서 clientA, clientB가 각각 의존관계 주입을 받으면 각각 다른 인스턴스의 프로토타입 빈을 주입 받는다. 
clientA -> prototypeBean@x01
clientB -> prototypeBean@x02 
물론 사용할 때 마다 새로 생성되는 것은 아니다.

 


해결방법

이에 대한 해결방법을 알아보자.

 

방법 1

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

  • ac.getBean() 을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다.
  • 의존관계를 외부에서 주입(DI) 받는게 아니라 이렇게 직접 필요한 의존관계를 찾는 것을 Dependency Lookup (DL) 의존관계 조회(탐색) 이라한다.

그런데 이렇게 스프링의 애플리케이션 컨텍스트 전체를 주입받게 되면, 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트도 어려워진다.

지금 필요한 기능은 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는 딱! DL 정도의 기능만 제공하는 무언가가 있으면 된다.

 

그리하여 다음 방법이 있다.

 

방법 2

ObjectFactory, ObjectProvider

지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것이 바로 ObjectProvider 이다.

참고로 과거에는 ObjectFactory 가 있었는데, 여기에 편의 기능을 추가해서 ObjectProvider 가 만들어졌다.

코드는 아래와 같다.

아래의 테스트는 통과한다.

출력결과:

다른 빈이 나왔다.

실행해보면 prototypeBeanProvider.getObject() 을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다.

ObjectProvidergetObject()를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다. (DL)
스프링이 제공하는 기능을 사용하지만, 기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워진다.

ObjectProvider 는 지금 딱 필요한 DL 정도의 기능만 제공한다.

 

ObjectFactory: 기능이 단순, 별도의 라이브러리 필요 없음, 스프링에 의존
ObjectProvider: ObjectFactory 상속, 옵션, 스트림 처리등 편의 기능이 많고, 별도의 라이브러리 필요 없음, 스프링에 의존

 

 

방법 3

JSR-330 Provider

이 방법은 자바 표준을 사용하는 방법이다. (사용을 위해서는 'javax.inject:javax.inject:1'을 Gradle에 추가해야함)

(테스트코드와 출력결과는 위의 ObjectProvider사용 부분과 같음.)

 

실행해보면 provider.get() 을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다. providerget()을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다. (DL)

자바 표준이고, 기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워진다.

Provider 는 지금 딱 필요한 DL 정도의 기능만 제공한다.

 

  • get() 메서드 하나로 기능이 매우 단순하다.
  • 별도의 라이브러리가 필요하다.
  • 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.

'Spring > spring 원리' 카테고리의 다른 글

웹 스코프  (0) 2021.06.23
빈 스코프  (0) 2021.06.22
Bean Lifecycle, callback  (0) 2021.06.21
조회되는 빈이 모두 필요한 경우  (0) 2021.06.20
조회되는 빈이 2개 이상이라면? + 어노테이션 직접만들기  (0) 2021.06.20