스프링부트

spring bean scope

알쓸개잡 2023. 10. 3.

Spring framework에서 중요한 개념 중 하나로 Bean의 scope를 설정하여 객체의 생성과 사용에 대한 제어를 할 수 있다. Spring에서 제공하는 Bean scope의 5가지 유형에 대해서 기록한다.

 

Spring Bean Scope

  • Singleton (default)
  • Prototype
  • Request
  • Session
  • Application

5가지 scope 유형 중에서 Singleton과 Prototype은 표준 범위라고 하며 ApplicationContext에서 사용할 수 있다. Requst, Session, Application 유형은 웹 기반 애플리케이션에서만 사용할 수 있다.

 

Singleton Scope

단 하나의 인스턴스만 생성할 수 있는 클래스는 singleton 이다. 애플리케이션의 어느 곳에서든 해당 객체에 access 하려고 할 때마다 동일한 인스턴스를 얻게 된다. 모든 Spring bean은 디폴트로 singleton이다. 아래와 같이 명시적으로 Bean을 singleton으로 선언할 수 있다.

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class Bean {}

 

두 개의 service class가 singleton bean을 주입받아 사용할 때 해당 bean이 동일한 인스턴스인지 확인을 해본다.

package com.example.bean.scope.singleton;

import org.springframework.stereotype.Component;

@Component
public class SingletonBean {
}
package com.example.bean.scope.service;

import com.example.bean.scope.singleton.SingletonBean;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class Service1 {
	private final SingletonBean singletonBean;

	public SingletonBean getSingletonBean() {
		return singletonBean;
	}
}
package com.example.bean.scope.service;

import com.example.bean.scope.singleton.SingletonBean;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class Service2 {
	private final SingletonBean singletonBean;

	public SingletonBean getSingletonBean() {
		return singletonBean;
	}
}

Service1, Service2 모두 SingletonBean 을 주입받아 사용하고 있다. SingletonBean 인스턴스가 동일한 인스턴스 인지 확인해 보자.

package com.example.bean.scope.singleton;

import com.example.bean.scope.service.Service1;
import com.example.bean.scope.service.Service2;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {
	Service1.class, Service2.class, SingletonBean.class
})
public class SingletonBeanTest {
	@Autowired
	Service1 service1;

	@Autowired
	Service2 service2;

	@Test
	void singleton_bean_test() {
		SingletonBean singletonBean1 = service1.getSingletonBean();
		SingletonBean singletonBean2 = service2.getSingletonBean();
		Assertions.assertThat(singletonBean1).isEqualTo(singletonBean2);
	}
}

위 테스트 코드는 pass 되어 동일한 인스턴스 빈을 사용하는 것을 알 수 있다.

 

Prototype Scope

prototype scope 는 해당 bean을 주입할 때마다 새로운 인스턴스가 주입된다.

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class Bean {}

Bean을 prototype scope로 지정해 보자.

package com.example.bean.scope.prototype;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class PrototypeBean {
}

Service1, Service2에 PrototypeBean을 주입한다.

package com.example.bean.scope.service;

import com.example.bean.scope.prototype.PrototypeBean;
import com.example.bean.scope.singleton.SingletonBean;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class Service1 {
	private final SingletonBean singletonBean;
	private final PrototypeBean prototypeBean;

	public SingletonBean getSingletonBean() {
		return singletonBean;
	}

	public PrototypeBean getPrototypeBean() {
		return prototypeBean;
	}
}
package com.example.bean.scope.service;

import com.example.bean.scope.prototype.PrototypeBean;
import com.example.bean.scope.singleton.SingletonBean;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class Service2 {
	private final SingletonBean singletonBean;
	private final PrototypeBean prototypeBean;

	public SingletonBean getSingletonBean() {
		return singletonBean;
	}

	public PrototypeBean getPrototypeBean() {
		return prototypeBean;
	}
}

Service1, Service2에 주입된 PrototypeBean이 서로 다른 인스턴스인지 확인해 보자.

package com.example.bean.scope.prototype;

import com.example.bean.scope.service.Service1;
import com.example.bean.scope.service.Service2;
import com.example.bean.scope.singleton.SingletonBean;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {
	Service1.class, Service2.class, SingletonBean.class, PrototypeBean.class
})
class PrototypeBeanTest {
	@Autowired
	Service1 service1;

	@Autowired
	Service2 service2;

	@Test
	void prototype_bean_test() {
		PrototypeBean prototypeBean1 = service1.getPrototypeBean();
		PrototypeBean prototypeBean2 = service2.getPrototypeBean();
		Assertions.assertThat(prototypeBean1).isNotEqualTo(prototypeBean2);
	}
}

위 테스트 코드는 pass 되어 서로 다른 PrototypeBean 인스턴스가 사용되는 것을 알 수 있다.

 

Request Scope

request scope는 웹 애플리케이션에서만 사용할 수 있다. 각 request는 request scope이 지정된 빈의 전용 인스턴스를 가져오고, 요청이 완료될 때까지 빈을 계속 사용할 수 있다.

 

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestBean {}
proxyMode = ScopedProxyMode.TARGET_CLASS를 지정하는 이유
bean의 scope이 request 범위에 있기 때문에 요청이 있을 때까지 bean의 인스턴스가 생성되지 않기 때문이다.
request scope을 가지는 bean을 주입받아 사용하는 클래스(controller나 service와 같은)는 웹 애플리케이션이 시작될 때 자동으로 인스턴스화하는데 이때 request scope을 가지는 bean을 바로 주입하는 것이 아닌 proxy 인스턴스를 생성하여 주입한다. controller가 요청을 받으면 proxy 인스턴스가 실제 request scope bean 인스턴스로 대체된다.

 

request scope을 가지는 bean을 생성하고 Service와 Controller에서 각각 request bean을 주입하여 request 내에서 동일한 bean 인스턴스가 사용되는지 확인해 보자.

package com.example.bean.scope.request;

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestBean {
}
package com.example.bean.scope.service;

import com.example.bean.scope.request.RequestBean;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class RequestService {
	private final RequestBean requestBean;

	public RequestBean getRequestBean() {
		return requestBean;
	}
}
package com.example.bean.scope.controller;

import com.example.bean.scope.request.RequestBean;
import com.example.bean.scope.service.RequestService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequiredArgsConstructor
public class RequestController {
	private final RequestBean requestBean;
	private final RequestService requestService;

	@GetMapping("/requestBean")
	public void requestBean() {
		log.info("request bean: {}", requestBean);
		log.info("service request bean: {}", requestService.getRequestBean());
	}
}

/requestBean을 2번 호출하면 아래와 같이 RequestBean이 request 내에서만 동일한 RequestBean 인스턴스가 사용되는 것을 확인할 수 있다.

//첫번째 request - controller와 service에서 사용하는 RequestBean 인스턴스가 동일함을 알 수 있다.
[nio-8080-exec-2] c.e.b.s.controller.RequestController     : request bean: com.example.bean.scope.request.RequestBean@4a676219
[nio-8080-exec-2] c.e.b.s.controller.RequestController     : service request bean: com.example.bean.scope.request.RequestBean@4a676219
//두번째 request - controller와 service에서 사용하는 ReqeustBean 인스턴스가 동일함을 알 수 있다.
//첫번째 request에서 사용된 RequestBean 인스턴스와 다른 RequestBean 인스턴스가 사용된다.
[nio-8080-exec-5] c.e.b.s.controller.RequestController     : request bean: com.example.bean.scope.request.RequestBean@762bf5a4
[nio-8080-exec-5] c.e.b.s.controller.RequestController     : service request bean: com.example.bean.scope.request.RequestBean@762bf5a4

 

Session Scope

session scope도 웹 애플리케이션에서만 사용할 수 있다. 동일한 session 내에서 전용 bean 인스턴스를 사용한다.

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionBean {}

session scope을 가지는 bean을 생성하고 Service와 Controller에서 각각 session bean을 주입하여 동일 session 내에서 동일한 bean 인스턴스가 사용되는지 확인해 보자.

 

package com.example.bean.scope.session;

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, 
	proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionBean {
}
package com.example.bean.scope.service;

import com.example.bean.scope.session.SessionBean;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class SessionService {
	private final SessionBean sessionBean;

	public SessionBean getSessionBean() {
		return sessionBean;
	}
}
package com.example.bean.scope.controller;

import com.example.bean.scope.request.RequestBean;
import com.example.bean.scope.service.RequestService;
import com.example.bean.scope.service.SessionService;
import com.example.bean.scope.session.SessionBean;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequiredArgsConstructor
public class SessionController {
	private final SessionBean sessionBean;
	private final SessionService sessionService;

	@GetMapping("/sessionBean")
	public void sessionBean() {
		log.info("session bean: {}", sessionBean);
		log.info("service session bean: {}", sessionService.getSessionBean());
	}
}

/sessionBean을 두 번 호출하면 아래와 같이 로그가 생성된다.

[nio-8080-exec-2] c.e.b.s.controller.SessionController     : session bean: com.example.bean.scope.session.SessionBean@6593ed8
[nio-8080-exec-2] c.e.b.s.controller.SessionController     : service session bean: com.example.bean.scope.session.SessionBean@6593ed8
[nio-8080-exec-3] c.e.b.s.controller.SessionController     : session bean: com.example.bean.scope.session.SessionBean@6593ed8
[nio-8080-exec-3] c.e.b.s.controller.SessionController     : service session bean: com.example.bean.scope.session.SessionBean@6593ed8

쿠키를 삭제 후 다시 /sessionBean을 두 번 호출하면 아래와 같이 로그가 생성된다.

[nio-8080-exec-4] c.e.b.s.controller.SessionController     : session bean: com.example.bean.scope.session.SessionBean@c47a0f8
[nio-8080-exec-4] c.e.b.s.controller.SessionController     : service session bean: com.example.bean.scope.session.SessionBean@c47a0f8
[nio-8080-exec-5] c.e.b.s.controller.SessionController     : session bean: com.example.bean.scope.session.SessionBean@c47a0f8
[nio-8080-exec-5] c.e.b.s.controller.SessionController     : service session bean: com.example.bean.scope.session.SessionBean@c47a0f8

서로 다른 session 내에서 다른 SessionBean 인스턴스가 사용됨을 알 수 있다.

 

Application Scope

웹 애플리케이션 내에서 한 번만 생성되는 bean이다. 애플리케이션이 시작될 때 생성되고 애플리케이션이 중지되면 소멸된다. singleton scope와 다르지 않지만 차이점은 singleton bean은 ApplicationContext에 생성되고 application scope bean은 WebApplicationContext에 생성된다.

@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ApplicationBean {}

예제 코드는 따로 작성하지 않겠다.

댓글

💲 추천 글