@Scope 어노테이션이 필요한 이유
우리가 Bean을 만들 때 기본적으로 싱글톤으로 만들어진다. 즉, 한 번 생성되면 해당 Bean이 필요한 곳 모두에서 재사용된다. 만약 순수 함수로만 구성된 Bean이거나 애플리케이션 실행 도중 하나의 공통된 상태만 유지하면 되는 객체라면 이렇게 싱글톤으로 생성한 후 모든 곳에서 재사용하면 된다.
하지만, 종종 상태를 가지면서 이 상태가 사용되는 곳마다 다르게 관리돼야 하는 Bean이 있다. 예를 들어 사용자를 위한 장바구니 객체를 만들었는데 사용자마다 다른 장바구니가 만들어져야 하는 경우가 있을 수 있다. 이런 경우에는 Bean이 필요할 때마다 새로운 인스턴스가 생성돼야 한다.
Spring은 이러한 상황들을 위해 Bean이 생성되는 방식을 제공하는 @Scope 어노테이션을 제공한다. 지금부터 @Scope Annotation을 사용해 Bean이 생성되는 범위를 어떻게 관리할 수 있을지 알아보자.
@Scope 어노테이션 사용해 Bean을 필요할 때마다 새로 만들기
필요할 때마다 새로운 인스턴스가 생성돼야 하는 Bean의 경우 @Component와 함께 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)을 붙여 Bean이 요청될 때마다 새로 만들어지게 할 수 있다. 예를 들어 우리가 쇼핑몰 앱을 만들고 여기서 사용할 쇼핑 카트 객체인 ShoppingCart가 필요할 때마다 새롭게 만들어져야 한다고 해보자. 그러면 이 ShoppingCart 객체는 다음과 같이 만들 수 있다.
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
class ShoppingCart {
private val items: MutableList<String> = mutableListOf()
fun addItem(item: String) {
items.add(item)
}
fun removeItem(item: String) {
items.remove(item)
}
}
이렇게 만들어진 ShoppingCart 객체는 getBean(ShoppingCart::class.java) 을 호출할 때마다 새롭게 만들어진다.
이를 확인하기 위해 다음 코드와 같이 getBean(ShoppingCart::class.java)을 두 번 호출하고 출력을 확인 해보자.
fun main(args: Array<String>) {
val context = AnnotationConfigApplicationContext(UserContainerConfiguration::class.java)
println(context.getBean(ShoppingCart::class.java))
println(context.getBean(ShoppingCart::class.java))
}
그러면 다음과 같은 결과가 나오고 다른 인스턴스가 생성되는 것을 볼 수 있다.
@Scope 어노테이션 사용해 Bean을 싱글톤으로 만들기
Bean을 싱글톤으로 만드는 경우 @Component와 함께 @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)을 붙인 클래스를 만들면 된다. 예를 들어 앞서 만든 ShoppingCart를 조작하는 객체인 CartController는 한 개만 있으면 되므로 다음과 같이 만들면 된다.
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
class CartController() {
fun addToCart(userId: String, item: String) {
println("[Add Item] User $userId added $item to cart")
}
}
그러면 이 CartProvider은 여러번 호출해도 같은 객체가 반환된다. 이를 확인하기 위해 getBean(CartController::class.java)를 두 번 호출하고 출력을 확인해보자.
fun main(args: Array<String>) {
val context = AnnotationConfigApplicationContext(UserContainerConfiguration::class.java)
println(context.getBean(CartController::class.java))
println(context.getBean(CartController::class.java))
}
그러면 결과가 다음과 같이 나오고 같은 인스턴스가 생성되는 것을 확인할 수 있다.
참고로 @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)는 기본 값이기 때문에 다음과 같이 생략해도 괜찮다.
@Component
class CartController() {
fun addToCart(userId: String, item: String) {
println("[Add Item] User $userId added $item to cart")
}
}
그런데 여기서 한 가지 의문이 생긴다. 그러면 이 ProtoType으로 선언된 ShoppingCart Bean을 싱글톤 Bean에서 사용하려면 어떻게 해야할까? 만약 다음과 같이 CartController에 주입을 받으면 CartController는 한 번만 생성되므로 ShoppingCart도 하나만 생성될 것이다.
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
class CartController(
private val shoppingCart: ShoppingCart
) {
fun addToCart(userId: String, item: String) {
println("[Add Item] User $userId added $item to cart")
}
}
이 문제를 해결하기 위해 Spring은 ObjectProvider이란 객체를 제공한다. 이어서 ObjectProvider을 사용해 필요한 Bean을 주입 받는 방법을 알아보자.
전체 코드: GitHub
이 프로젝트가 도움이 되셨다면 저장소에 Star⭐️를 눌러주세요! Stargazers는 다음 페이지에서 확인할 수 있습니다.