[Spring] 09-1. 프로토타입 스코프 빈

지찬우·2023년 1월 17일
0

Spring

목록 보기
26/27
post-thumbnail

이 시리즈는 인프런 강의(김영한 님의 ‘스프링 핵심 원리 - 기본편’)로 공부하며 혼자 기록하고, 사람들과도 공유할 수 있도록 작성하는 글이다. 최대한 추가적인 정보는 공식 홈페이지, 문서를 보며 얻을 예정이다.
(개인적인 생각과 이해가 들어가 있기 때문에 저의 ‘무식함’이 있을 수 있습니다😜 혹시라도 이 글을 보게 되시는 분이 계시다면 잘못된 부분 댓글로 많이 알려주시면 너무 감사하겠습니다!!)

GitHub Repository : https://github.com/jcw1031/spring-core-study


빈 스코프(Bean Scopes)

스프링 컨테이너가 시작되면 스프링 빈도 함께 생성되어 스프링 컨테이너가 종료될 때까지 유지된다. 스프링 빈이 기본적으로 싱글톤 빈으로 생성되기 때문이다. 스코프(Scopes)는 이처럼 빈이 존재할 수 있는 범위(시간개념)를 뜻한다.

스코프의 종류

스코프에는 여러 가지 종류가 있다.

  • 싱글톤 : 위에서 설명했던 기본 스코프이다.
  • 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는
    매우 짧은 범위의 스코프이다.
  • 웹 관련 스코프
    • request : 웹 요청이 들어오고 나갈 때까지 유지되는 스코프이다.
    • session : 웹 세션이 생성되고 종료될 때까지 유지되는 스코프이다.
    • application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프이다.

스코프는 클래스 위에 애노테이션을 추가하여 지정할 수 있다.

@Scope("스코프이름")

// ex) @Scope("prototype") -> 프로토타입 스코프

프로토타입 스코프 📌

프로토타입 스코프 빈은 스프링 컨테이너에 조회할 때마다 항상 새로운 인스턴스를 생성해 반환한다. 객체를 생성하고 의존관계 주입을 마친 후 반환하고 더 이상 관여하지 않는다. 따라서 반환된 빈은 클라이언트가 관리하게 된다.

클라이언트 C의 memberService 요청은 오타인 듯하다. 아마 prototypeBean 요청이 맞는 것 같다.

핵심은 스프링 컨테이너는 프로토타입 스코프 빈을 생성하고 의존관계 주입, 초기화까지만 처리한다는 점이다. 클라이언트에게 빈을 반환하고 이후에 반환된 프로토타입 빈을 관리하지 않는다. 그래서 @PreDestroy같은 종료 메서드가 호출되지 않는다. (종료 메서드 호출이 필요한 경우 클라이언트가 직접 호출해야 한다.)


아래와 같이 프로토타입 빈을 만들어 테스트해 보면 확인할 수 있다. 또한 결과를 확인해 보면, 프로토타입 스코프 빈은 컨테이너 생성 시점에 초기화 메서드가 실행되지 않고, 빈을 조회할 때 초기화 메서드가 실행되는 것을 알 수 있다. 종료 메서드는 아예 호출되지 않는다.

프로토타입 스코프 빈

  • 스프링 컨테이너에 요청할 때마다 새로 생성된다.
  • 스프링 컨테이너는 프로토타입 스코프 빈의 생성과 의존관계 주입, 초기화까지만 관여한다.
  • 종료 메서드가 호출되지 않는다.

문제점 ❗️

싱글톤 스코프 빈이 의존관계 주입을 통해 프로토타입 스코프 빈을 주입받아 사용하는 경우 문제가 발생할 수 있다.

아래와 같은 로직들을 갖는 프로토타입 스코프 빈이 있다.


이 프로토타입 스코프 빈을 단일로 사용하면, 클라이언트가 빈을 요청하여 반환받고 로직을 수행해 필드의 값을 변경해도 다른 클라이언트가 또 프로토타입 스코프 빈을 요청해 반환받았을 때 필드의 값은 초깃값으로 되어있을 것이다. 클라이언트가 요청할 때마다 새로운 빈을 생성해 반환하기 때문에 당연한 결과이다.


하지만 만약 싱글톤 스코프 빈이 주입받은 프로토타입 스코프 빈의 경우는 어떨까. 아래와 같이 싱글톤 스코프 빈이 의존관계 주입으로 프로토타입 스코프 빈을 사용하고, logic() 메서드에서 프로토타입 스코프 빈의 필드 값을 변경하고 반환받는 로직을 갖고 있다.


이 경우에는, 처음에 싱글톤 스코프 빈이 생성되고 의존관계를 주입받을 때 프로토타입 스코프 빈이 생성되어 주입될 것이다. 그 이후부터는 싱글톤 스코프 빈이 프로토타입 스코프 빈을 관리하게 된다. 싱글톤 스코프 빈이 죽기 전까지는 하나의 프로토타입 스코프 빈을 참조하고 있는 것이다. 아까의 경우와는 다르게 여러 클라이언트가 이 싱글톤 스코프 빈을 반환받아 logic() 메서드를 호출하게 되면, 프로토타입 스코프 빈의 필드 값은 계속해서 변경될 것이다. 클라이언트 B는 count 값 1을 기대했지만, 클라이언트 A가 이미 logic()을 한 번 호출했기 때문에 클라이언트 B는 2를 반환받을 것이다.

우리가 원하는 것은 사용할 때마다 새로 생성해서 사용하는 것이다. 하지만 프로토타입 스코프 빈이 사용할 때마다 생성되는 것이 아니라 스프링 컨테이너로부터 조회할 때 새로 생성되는 것임을 잊지 말자.


해결 방안 💡

우리가 원하는 대로 만드는 데에는 여러 개의 방법이 있다. 무식하게 싱글톤 스코프 빈에서 컨테이너를 주입받아 logic()이 호출될 때마다 프로토타입 스코프 빈을 조회하여 로직을 수행하는 방법이다. 바로 이렇게.

하지만 이렇게 ApplicationContext 전체를 주입받으면 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트가 어려워진다.

의존관계를 외부에서 주입받는 것은 DI, 필요한 의존관계를 직접 찾는 것은 ‘DL(Dependency Lookup)’, 즉 ‘의존관계 조회(탐색)’이라고 한다.


ObjectFactory, ObjectProvider 📌

딱 DL 정도의 기능만 제공해 주는 것이 ObjectFactoryObjectProvider 인터페이스이다. ObjectFactory를 상속받는 ObjectProvider는 편의 기능이 좀 더 추가된 인터페이스이다.

아래처럼 사용한다. ObjectProvidergetObject() 메서드를 호출해 내부에서 스프링 컨테이너를 통해 해당 빈을 찾아서 반환받을 수 있다.

스프링이 제공하는 기능을 사용하지만, 기능이 단순해 단위 테스트를 만들거나 mock 코드를 만들기 쉽다.

ObjectFactory, ObjectProvider의 핵심 컨셉은 스프링 컨테이너에 대신 빈을 조회해 주는 것이지 프로토타입 스코프 빈 전용 인터페이스가 아니다!

참고 : ObjectProvider는 스프링 빈으로 등록되었다는 로그가 안 찍히지만, 스프링이 알아서 처리해 준다고 한다.


Provider 📌

자바 표준 JSR-330의 Provider 인터페이스도 같은 기능을 제공한다. 이 방법을 사용하려면 build.gradle에 라이브러리를 추가해 주어야 한다. (새로고침을 해주어야 적용된다.)

implementation ‘javax.inject:javax.inject:1


Providerget() 메서드가 ObjectProvidergetObject()와 같은 역할을 한다. 사용 방법은 같다.

별도의 라이브러리가 필요하지만 자바 표준이고 기능이 단순하기 때문에 단위 테스트나 mock 코드를 만들기 훨씬 쉬워진다. 또한 자바 표준이기 때문에 스프링이 아닌 다른 컨테이너에서도 사용이 가능하다.


정리 📝

  • 매번 사용할 때마다 의존관계 주입이 완료된 새로운 객체가 필요한 경우에 프로토타입 스코프 빈을 사용하면 된다. 하지만 실무에서는 싱글톤 스코프 빈으로 대부분의 문제를 해결할 수 있기 대문에 프로토타입 스코프 빈을 직접적으로 사용하는 일은 매우 드물다.
  • ObjectProviderProvider 등은 프로토타입 스코프 빈 뿐만 아니라 DL이 필요한 경우는 언제든 사용이 가능하다. (예시로는 순환 참조 시에 사용할 수 있다.)

스프링이 제공하는 @Lookup 애노테이션을 사용하는 방법도 있지만, 잘 사용되지 않는다.

결론은 기능이 비슷하거나 스프링이 표준을 권장한다면 표준을 사용하고, 스프링에서 제공하는 기능이 더 편리하고, 특별히 다른 컨테이너를 사용할 일이 없다면 스프링이 제공하는 기능을 사용하면 된다.

profile
좋은 개발자가 되자.

0개의 댓글