싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.
프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다.
웹 관련 스코프
request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프이다.
session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프이다.
application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프이다.
프로토타입 빈
매 객체 요청마다 싱글톤 처럼 같은 객체를 받는게 아니라 계속 새로운 객체를 받음
스프링 컨테이너는 프로토타입 빈을 생성, 의존관계 주입, 초기화 까지만 처리하고 그 뒤에 반환이나 관리는 하지 않음
따라서, 이에 대한 관리는 클라이언트가 직접 해줘야 하며 @PreDestory같은 어노테이션은 동작하지 않음
사실상 실무에서 거의 싱글톤 빈만 사용하지 프로토 타입은 별로 사용할 일은 없음
프로로타입 빈을 싱글톤 빈과 함께 사용할 시 문제점
위 그림에서 clientBean은 싱글톤 빈이고 싱글톤 빈 안에 PrototypeBean을 생성해서 사용중임.
프로토타입 빈을 사용하는 이유는 매 요청때마다 새로운 프로로타입을 할당받기 위함임
하지만, 싱글톤 빈은 처음부터 끝까지 계속 같은 객체고, 싱글톤 빈안에 생성된 프로토타입 빈 또한 한 번 할당된 이후에는 변할 수 없음.
따라서, 클라이언트A가 addCount() 로직을 호출해서 프로토타입 빈의 count 값을 1 올린 후, 클라이언트B가 같은 로직을 호출 시, 새로운 프로토타입에서 count를 1 올린게 아닌 클라이언트A가 올린 값에서 1을 더 올리게 됨.
이는, 프로토타입을 생성한 의도와 달라지는 문제가 발생함.
프로토타입 빈을 싱글톤 빈과 함께 사용시 DL을 통해 해결
@Scope("prototype")
@Component
static class PrototypeBean {
private int count = 0;
public void addCount() {
count++;
}
public int getCount() {
return count;
}
...
}
static class ClientBean {
@Autowired
private ApplicationContext ac;
public int logic() {
PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
싱글톤 빈에서 프로토타입 빈을 생성할 때 객체 주입으로 받지 않고 직접 필요한 의존관계를 찾아서 매번 넣어 줌.
이 방식을 Dependency Lookup(DL) 의존관계 탐색이라고 함.
하지만, 이 방법은 스프링 컨테이너에 종속적인 코드가 되며 단위 테스트에 어려움이 생김.
스프링 컨테이너의 종속성은 없애고 DL만 사용하는 방법이 필요
ObjectFactory, ObjectProvider
public class SomeClass {
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
ObjectProvider는 DL 서비스를 제공함.
과거에는 ObjectFactory 였는데 여기에 편의 기능이 추가되어 ObjectProvider가 만들어짐
단위테스트 하기 훨씬 쉬워짐.
JSR-330 Provider
@Autowired
private Provider<PrototypeBean> provider;
public int logic() {
PrototypeBean prototypeBean = provider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
javax.inject.Provider라는 JSR-330 자바 표준
스프링이 아니라 자바 표준
DL 기능을 제공
웹 스코프
웹 스코프는 웹 환경에서만 동작
프로토타입과 다르게 스프링이 해당 스코프의 종료 시점까지 관리해주기 때문에 종료 메서드가 호출됨
웹스코프 종류
request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴 스가 생성되고, 관리된다.
session: HTTP Session과 동일한 생명주기를 가지는 스코프
application: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프
websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프
위 그림은 request 스코프 예제이며, 요청이 들어올 때마다 별도의 requset scope의 Bean이 만들어지며, 같은 Requset 내에서 Controller가 사용하는 Bean과 Service가 사용하는 Bean은 같음.
Error creating bean with name 'myLogger': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton;
왜냐 하면, request scope 빈은 실제로 요청이 들어와야 Bean이 생성이 되는데, Controller나 Service 는 일반적으로 싱글톤 빈으로 생성이 되어 있고, 싱글톤 빈은 애플리케이션이 실행될 때 생성이 되는데 이 때 생성자 주입으로 request scope 빈을 넣으려고 하면 해당 빈은 아직 생성이 되어있지 않기 때문
Provider를 통한 Requset Scope 빈의 생성 시점 문제 해결
private final ObjectProvider<MyLogger> myLoggerProvider;
MyLogger myLogger = myLoggerProvider.getObject();
위 코드를 사용하면, 에러 없이 동작이 가능함
그 이유는 Provider는 getObject() 함수를 호출 할 때, 실제로 해당 빈이 필요해지면 빈을 생성하고 주입함.
이 방식은 지연 주입 방식이며 빈이 실제로 사용되기 전까지는 생성되지 않음
따라서, 이전의 프로토타입 빈의 방식에서도 프로토타입은 미리 생성이 되어 있지 않아도 요청이 올때 딱 생성해서 줌.
프록시를 통한 Requset Scope 빈의 생성 시점 문제 해결
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}
proxyMode = ScopedProxyMode.TARGET_CLASS 를 추가
적용 대상이 인터페이스가 아닌 클래스면 TARGET_CLASS 를 선택
적용 대상이 인터페이스면 INTERFACES 를 선택
이 방법을 사용하면 이전의 ObjectProvider 같은 복잡한 내용 없이 기존의 내용처럼 사용 가능
이 방법은 CGLIB이라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입해줌
가짜 프록시는 요청이 오면 그 때 내부에서 진짜 빈을 요청하는 위임 로직이 들어가 있음.
클라이언트들은 myLogger.log를 호출하지만 실제로는 가짜 프록시 메서드를 호출
가짜 프록시 객체는 진짜 myLogger.log를 호출
클라이언트 입장에서는 진짜든 가짜든 상관이 없지만 원하는 결과를 얻을 수 있음.(다형성)
동작 정리
CGLIB이라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입
가짜 프록시 객체는 실제 요청이 오면 내부에서 실제 빈을 요청하는 위임 로직이 있음
가짜 프록시는 실제 request scope과 관련이 전혀 없고 진짜 mock이고 내부에 단순 위임 로직만 있고 싱글톤임
특징 정리
프록시 객체 때문에 마치 싱글톤 빈 처럼 requset scope 빈을 사용 가능
Provider든 Proxy든 결국 핵심 아이디어는 객체 조회를 필요한 시점까지 지연처리 한다는 것
어노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있으며, 이게 바로 다형성과 DI의 강점
웹 스코프가 아니라도 프록시는 사용 가능함
주의 점
싱글톤과 비슷해 보이지만 실제론 다르게 동작하므로 주의해야 함
requset scope와 같은 이러한 스코프는 꼭 필요한 곳에서만 최소화해서 사용해야지, 무분별하게 사용하면 유지보수성이 어려워짐.