[Spring] 빈 스코프란?
스프링의 빈 스코프를 알아보자
📌 서론
스프링으로 개발을 하다 보면 스프링 빈을 정말 많이 사용하게 된다. 요즘은 "모 개발자(영한쌤)의 강의"를 잘 듣는다면 스프링 빈에 대한 개념을 정말 잘 배울 수 있다고 생각한다. 나도 영한쌤의 강의를 들어가며 성장했고 덕분에 스프링에 대해 단단하게 기초를 잡고 현업에 적응할 수 있었다. 이렇게 무럭무럭 성장하는 주니어 시절을 보내던 와중 스프링의 빈 등록 원리를 더 자세히 알고 싶다는 생각이 들어 겁도 없이 공식 가이드 자료를 살펴보기 시작했다.
나는 가끔 스프링의 공식 업데이트 내용을 보고 싶어서 가이드 정보를 구경하러 들어갔기에 처음 확인한 것은 아니지만 이 정도로 자세히 살펴본 적은 없었다. 이번에 가이드를 자세히 읽으면서 계속해서 놀랐다. 왜냐하면 영어로 되어있긴 하지만 스프링의 원리에 대해 엄청 자세하게 정보들이 적혀있었다. (다만 이걸 지금 개발 시작하는 주니어가 본다면 용어 때문에 혀를 내두를 것 같긴 하다.)
주저리주저리 떠들었지만 결론적으로 내가 이번 글을 작성하게 된 이유는 이런 좋은 정보를 조금이나마 기록해 둔다면 누군가 검색해 보면서 도움을 받을 수 있지 않을까? 생각하고 정리를 시작했다. 물론 나도 이렇게 기록하며 공부를 하기 때문인 것도 있다!
그럼 지금부터 함께 스프링 빈을 알아보자! (정리한 내용은 모두 아래의 공식 가이드를 번역하여 적어둔 내용이다.)
1. 스프링의 빈 스코프
빈 스코프가 뭔가요?
- 스프링에서 빈(Bean) 정의를 생성할 때, 그 정의에 따라 실제 클래스 인스턴스를 만드는 "레시피"를 만들게 된다. 가이드에서는 이 레시피의 개념이 중요하다고 한다. 왜냐하면 하나의 레시피로 여러 개의 객체 인스턴스를 만들 수 있기 때문이다.
- 빈 정의로부터 생성된 객체에 주입될 여러 의존성과 설정 값을 제어할 수 있을 뿐만 아니라, 특정 빈 정의에서 생성된 객체의 범위(Scope)도 제어할 수 있다. 이 방식은 매우 강력하고 유연하다. 객체의 범위를 Java 클래스 수준에서 고정하지 않고, 설정을 통해 객체의 범위를 선택할 수 있기 때문이다.
- 스프링 빈은 여러 범위 중 하나로 정의될 수 있다. Spring Framework는 6가지의 범위를 지원하며, 이 중 4가지는 웹 애플리케이션을 인식하는 ApplicationContext에서만 사용 가능하다. 또한, 사용자 정의 범위를 만들 수도 있다.
범위 | 설명 |
singleton | (기본값) 하나의 빈 정의에 대해 Spring IoC 컨테이너당 하나의 객체 인스턴스만 생성된다. |
prototype | 하나의 빈 정의에 대해 여러 개의 객체 인스턴스를 생성할 수 있다. |
request | 하나의 빈 정의에 대해 HTTP 요청의 생명주기 동안 단일 객체 인스턴스를 생성한다. 즉, 각 HTTP 요청마다 해당 빈 정의로부터 생성된 고유한 인스턴스가 존재한다. 웹 어플리케이션을 인식하는 Spring ApplicationContext에서만 유효하다. |
session | 하나의 빈 정의에 대해 HTTP 세션의 생명주기 동안 단일 객체 인스턴스를 생성한다. 웹 어플리케이션을 인식하는 Spring ApplicationContext에서만 유효하다. |
application | 하나의 빈 정의에 대해 ServletContext의 생명주기 동안 단일 객체 인스턴스를 생성한다. 웹 어플리케이션을 인식하는 Spring ApplicationContext에서만 유효하다. |
websocket | 하나의 빈 정의에 대해 WebSocket의 생명주기 동안 단일 객체 인스턴스를 생성한다. 웹 어플리케이션을 인식하는 Spring ApplicationContext에서만 유효하다. |
2. 스프링의 싱글톤 스코프
스프링에서의 싱글톤 스코프란?
- 싱글톤 빈은 단 하나의 공유 인스턴스만 관리되며, 해당 빈 정의와 일치하는 ID를 가진 모든 요청은 Spring 컨테이너가 반환하는 동일한 빈 인스턴스를 받게 된다. 다시 말해, 빈 정의를 만들고 그 범위를 싱글톤으로 설정하면, Spring IoC 컨테이너는 해당 빈 정의로 명시된 객체의 인스턴스를 정확히 하나만 생성한다.
- 이 인스턴스는 싱글톤 빈의 캐시에 저장되며, 이후 같은 이름으로 요청하거나 참조할 때마다 캐시 된 객체가 반환된다. 아래 이미지는 싱글톤 범위가 어떻게 작동하는지를 보여준다.
Spring의 싱글톤은 GoF의 싱글톤과는 다르다.
- Spring의 싱글톤 빈 개념은 GoF(Gang of Four) 패턴 책에서 정의된 싱글톤 패턴과는 다르다. GoF 싱글톤 패턴은 특정 클래스의 인스턴스가 클래스 로더당 하나만 생성되도록 범위가 하드 코딩되어 있다. 반면 Spring의 싱글톤 범위는 "컨테이너별" 및 "빈별"로 설명하는 것이 가장 적합하다. 즉, 특정 Spring 컨테이너에서 특정 클래스에 대한 빈을 하나 정의하면, Spring 컨테이너는 그 빈 정의로 명시된 클래스의 인스턴스를 단 하나만 생성한다.
싱글톤 범위는 Spring에서 기본적으로 설정되는 범위이다. XML로 빈을 싱글톤으로 정의하려면, 다음 예시와 같이 정의할 수 있다.
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- 아래와 같이 정의하는 것도 동일하지만, 중복됩니다 (싱글톤 범위가 기본값이기 때문입니다) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
- 이와 같이, 기본적으로 Spring에서 모든 빈은 싱글톤 범위로 설정되어 있으며, 이를 통해 애플리케이션에서 빈 인스턴스를 효율적으로 관리할 수 있다.
3. 스프링의 프로토타입 스코프
스프링에서 프로토타입 스코프란?
- 프로토타입 범위의 빈은 해당 빈에 대한 요청이 있을 때마다 새로운 인스턴스가 생성된다. 즉, 다른 빈에 주입되거나, 컨테이너의 "getBean()" 메서드를 호출하여 빈을 요청할 때마다 새로운 객체가 생성된다.
- 일반적으로 상태를 유지하는(stateful) 빈에는 프로토타입 범위를 사용하고, 상태를 유지하지 않는(stateless) 빈에는 싱글톤 범위를 사용해야 한다. (이것은 동시성 문제 때문인 것 같다.)
아래 이미지는 Spring의 프로토타입 범위를 보여준다.
- DAO (Data Access Object)는 대개 프로토타입으로 설정하지 않는다. 왜냐하면 일반적인 DAO는 상태를 가지지 않기 때문이다.
다음은 XML에서 빈을 프로토타입으로 정의하는 예시다.
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
- 다른 범위와 달리, Spring은 프로토타입 빈의 전체 생명주기를 관리하지 않는다. 컨테이너는 프로토타입 객체를 인스턴스화하고 설정하며 조립한 후, 해당 인스턴스를 클라이언트에게 전달한다. 그 이후에는 그 프로토타입 인스턴스에 대한 어떠한 기록도 남기지 않는다. 따라서 모든 범위에 대해 초기화 생명주기 콜백 메서드는 호출되지만, 프로토타입의 경우 설정된 소멸 생명주기 콜백은 호출되지 않는다.
- 클라이언트 코드에서 프로토타입 범위의 객체를 정리하고, 프로토타입 빈이 보유한 비싼 리소스를 해제해야 한다. 프로토타입 범위의 빈이 사용하는 리소스를 Spring 컨테이너가 해제하도록 하려면, 정리가 필요한 빈에 대한 참조를 유지하는 "커스텀 빈 후처리기(bean post-processor)를 사용하는 것이 좋다.
- 어떤 면에서 보면, 프로토타입 범위의 빈에 대한 Spring 컨테이너의 역할은 Java의 "new"연산자를 대체하는 것과 비슷하다. 그 이후의 모든 생명주기 관리는 클라이언트가 처리해야 한다. 이처럼 프로토타입 범위는 필요한 만큼 빈 인스턴스를 생성할 수 있는 유연성을 제공하지만, 리소스 관리와 정리에 대한 책임은 클라이언트가 직접 맡아야 한다.
4. 프로토타입 빈을 의존하는 싱글톤 빈
주의할 점
- 싱글톤 범위의 빈이 프로토타입 범위의 빈을 의존할 때, 주의해야 할 점이 있다. 의존성은 빈이 인스턴스화될 때 결정된다. 따라서, 싱글톤 범위의 빈에 프로토타입 범위의 빈을 주입하면, 새로운 프로토타입 빈 인스턴스가 생성되어 싱글톤 빈에 주입된다. 이 프로토타입 인스턴스는 해당 싱글톤 빈에 공급되는 유일한 인스턴스가 된다. (즉, 프로토타입으로 만든 게 싱글톤에 들어가니 결국 싱글톤이 된다.)
- 하지만, 런타임 중에 싱글톤 범위의 빈이 반복적으로 새로운 프로토타입 빈 인스턴스를 얻어야 하는 상황이 있을 수 있다. 이 경우, 프로토타입 범위의 빈을 싱글톤 빈에 직접 주입할 수 없다. 주입은 단 한 번, Spring 컨테이너가 싱글톤 빈을 인스턴스화하고 그 의존성을 해결하여 주입할 때만 발생하기 때문이다. 만약 런타임 중에 여러 번 새로운 프로토타입 빈 인스턴스를 필요로 한다면, "메서드 인젝션(Method Injection)을 고려해야 한다.
이처럼, 싱글톤 빈이 프로토타입 빈을 필요로 할 때는 의존성 주입의 동작 방식을 이해하고 적절한 방법을 사용하여 필요한 시점마다 새로운 프로토타입 인스턴스를 얻어야 한다.