1. Bean Scope
//컴포넌트 스캔 자동 등록
@Scope("prototype")
@Component
public class TestBean {}
//수동 등록
@Scope("prototype")
@Bean
PrototypeBean TestBean() {
return new TestBean();
}
- 빈이 존재할 수 있는 범위를 의미한다.
- 스프링 빈은 기본적으로 싱글톤 스코프로 생성되기 때문에, 스프링 컨테이너의 시작과 함께 생성되어서 스프링 컨테이너가 종료될 때까지 유지된다.
2. Scope 종류
종류 | 설명 |
singleton | - 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다. - 하나의 bean 정의에 대해서 컨테이너 내에 단 하나의 객체만 존재한다. |
prototype | - 스프링 컨테이너가 프로토타입 빈의 생성과 의존관계 주입, 그리고 초기화까지만 관여하고 더는 관여하지 않는 비교적 짧은 범위의 스코프이다. - 모든 요청에서 새로운 객체를 생성한다. - 프로토타입 빈을 받은 클라이언트가 객체를 관리해야 한다. 종료 메서드가 따로 호출되지 않기 때문에, 클라이언트가 직접 종료 메서드를 호출 해야한다. - 하나의 bean 정의에 대해서 다수의 객체가 존재할 수 있다. |
request | - 웹 요청이 들어오고 나갈 때까지 유지되는 스코프이다. - 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다. 즉, 각각의 HTTP request는 자신만의 객체를 가진다. |
session | - 웹 세션이 생성되고 종료될 때까지 유지되는 스코프이다. - 하나의 HTTP Session의 생명주기 안에 단 하나의 객체만 존재한다. |
global session | - 하나의 global HTTP Session의 생명주기 안에 단 하나의 객체만 존재한다. 일반적으로 portlet context 안에서 유효하다. |
application | - 웹의 ServletContext와 동일한 생명주기를 가지는 스코프이다. |
websocket | - 웹 소켓과 동일한 생명주기를 가지는 스코프이다. |
3. Singleton vs. Non-Singleton
구분 | 종류 |
싱글톤으로 적합한 객체 | ① 상태가 없는 공유 객체: 상태를 가지고 있지 않은 객체는 동기화 비용이 없기 때문에 매번 해당 객체를 참조하는 곳에서 새로운 객체를 생성할 이유가 없다. ② 읽기용으로만 상태를 가진 공유 객체: 상태를 가지고 있지만 읽기 전용이므로 여전히 동기화 비용이 들지 않는다. 따라서 매 요청마다 새로운 객체를 생성할 필요가 없다. ③ 공유가 필요한 상태를 지닌 공유 객체: 객체 간 반드시 공유해야 할 상태를 지닌 객체가 하나 있는 경우, 쓰기 접근에 대한 고려가 있다면 싱글톤이 적합할 수도 있다. ④ 쓰기가 가능한 상태를 지니면서도 사용 빈도가 매우 높은 객체: 애플리케이션 안에서 사용 빈도가 매우 높다면, 쓰기 접근에 대한 동기화 비용을 감안하고서라도 싱글톤을 고려할 수 있다. 특히 장시간에 걸쳐 매우 많은 객체가 생성되고, 해당 객체가 매우 적은 양의 쓰기 상태를 가지고 있을때, 그리고 객체 생성 비용이 매우 클 때에 가장 적합하다고 볼 수 있다. |
비싱글톤으로 적합한 객체 | ① 쓰기가 가능한 상태를 지닌 객체: 쓰기가 가능한 상태가 많아서 동기화 비용이 객체 생성 비용보다 크다면 싱글톤으로 적합하지 않다. ② 상태가 노출되지 않은 객체: 내부 상태를 외부에 노출하지 않는 빈을 참조하여 다른 의존 객체와는 독립적으로 작업을 수행하는 의존 객체가 있다면, 싱글톤보다 비싱글톤 객체를 사용하는 것이 나을 수도 있다. |
4. 싱글톤 빈과 프로토타입 빈을 함께 사용하는 방법
- 스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 된다. 그렇기 때문에 프로토타입 빈이 새로 생성되기는 하지만, 생성 시점에만 의존관계 주입을 받는 싱글톤 빈과 함께 계속 유지되는 문제가 발생할 수 있다.
- 문제 해결 방법
종류 | 설명 |
스프링 컨테이너에 요청 | - 싱글톤 빈이 프로토타입을 사용할 때마다 스프링 컨테이너에 새롭게 요청한다. - 의존관계를 외부에서 주입(DI) 받는게 아니라, 직접 필요한 의존관계를 찾는다(DL). - 그러나 스프링의 애플리케이션 컨텍스트 전체를 주입받게 되기 때문에 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트도 어려워진다. |
ObjectFactory, ObjectProvider | - ObjectProvider는 지정한 빈을 컨테이너에서 대신 찾아주는 DL 기능을 제공한다. - 과거에는 ObjectFactory가 위의 기능을 제공했는데, 여기에 편의 기능이 추가된 것이 ObjectProvider이다. - ObjectFactory: 기능이 단순하고 별도의 라이브러리가 필요 없다. 스프링에 의존한다. - ObjectProvider: ObjectFactory를 상속 받는다. 옵션, 스트림 처리 등 편의 기능이 많고 별도의 라이브러리가 필요 없다. 스프링에 의존한다. - getObject() 메서드를 통해서 항상 새로운 프로토타입 빈이 생성된다. 해당 메서드가 호출되면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다(DL). - 스프링이 제공하는 기능을 사용하지만, 기능이 단순하기 때문에 단위테스트를 만들거나 mock 코드를 만들기 훨씬 쉽다. - 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야 하는 경우만 아니면, 프로토타입 빈 생성에 가장 많이 사용한다. |
JSR-330 Provider | - javax.inject.Provider라는 JSR-330 자바 표준을 사용하는 방법이다. 이 방법을 사용하려면 javax.inject:javax.inject:1 라이브러리를 gradle에 추가해야 한다. - get() 메서드를 통해서 항상 새로운 프로토타입 빈이 생성된다. 해당 메서드가 호출되면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다(DL). - 자바 표준이고, get() 메서드 하나로 기능이 매우 단순하므로 단위테스트를 만들거나 mock 코드를 만들기 쉽다. - 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다. |
//ObjectProvider 예시
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
//JSR-330 Provider 예시: Provider 인터페이스
public interface Provdier<T> {
T get();
}
//JSR-330 Provider 예시: 실행 코드
@Autowired
private Provider<PrototypeBean> provider;
public int logic() {
PrototypeBean prototypeBean = provider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
4.1 DL(Dependency Lookup)
- 의존관계를 외부에서 주입(DI) 받는게 아니라, 직접 필요한 의존관계를 찾는 것을 의미한다.
- ObjectProvider, JSR330-Provider 등은 프로토타입 뿐만 아니라 DL이 필요한 경우는 언제든지 사용할 수 있다.
5. 스프링 애플리케이션을 실행하는 시점에서 웹 스코프 빈을 생성하는 방법
- 스프링 애플리케이션을 실행하는 시점에 싱글톤 빈은 생성해서 주입이 가능하지만, 웹 스코프 빈은 실제 고객의 요청이 와야 생성되기 때문에, 생성이 되지 않는다.
- 문제 해결 방법
종류 | 설명 |
ObjectProvider | - 진짜 객체 조회를 꼭 필요한 시점까지 지연처리한다. - ObjectProvider.getObject() 메서드를 호출하는 시점까지 request scope 빈의 생성을 지연할 수 있다. - ObjectProvider.getObject() 메서드를 호출하는 시점에는 HTTP 요청이 진행 중이므로 request scope 빈의 생성이 정상 처리된다. |
Proxy | - 진짜 객체 조회를 꼭 필요한 시점까지 지연처리한다. - proxyMode = ScopedProxyMode.TARGET_CLASS를 추가해서 설정할 수 있다. - 적용 대상이 클래스인 경우 TARGET_CLASS를, 인터페이스인 경우 INTERFACE를 선택하면 된다. - 해당 설정을 하면 스프링 컨테이너는 CGLIB라는 바이트코드를 조작하는 라이브러리를 사용해서 진짜 클래스를 상속 받은 가짜 프록시 클래스를 만들고, HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입할 수 있다. - 가짜 프록시 객체는 실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있다. - 반드시 웹 스코프가 아니어도 proxy를 사용할 수 있다. |
//Proxy 예시
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class TestBean {}
참고
- 『스프링 핵심 원리 - 기본편』
'Spring' 카테고리의 다른 글
Logging (0) | 2021.11.11 |
---|---|
DispatcherServlet (0) | 2021.11.08 |
Bean LifeCycle & Callback (0) | 2021.10.11 |
의존관계 주입 시 bean이 2개 이상일 때 해결 방법 (0) | 2021.10.11 |
Lombok library가 제공하는 애노테이션 (0) | 2021.10.11 |