빈 스코프 : 빈이 존재할 수 있는 범위 #
기본 값 : singleton (애플리케이션 시작 ~ 종료)
즉, 스프링 컨테이너의 시작과 끝 (동일한 범위를 갖는다.)
스코프 종류 #
singleton, prototype, request 는 기억하고 넘어갈 것
- singleton
- prototype
- 빈 생성 / 의존관계 / 초기화하여 반환하고 끝 (관리 X)
- 매우 짧은 범위의 스코프
- request (웹 관련 scope)
- 웹 요청이 들어오고 나갈 때 까지의 스코프
- session (웹 관련 scope)
- 웹 세션 생성 ~ 종료 스코프
- application (웹 관련 scope)
- 웹 서플릿 컨텍스트와 같은 범위
웹스코프 (라이브러리 필요 : spring-boot-starter-web)
- 웹 환경에서만 동작
- 해당 스코프의 종료시점까지 관리 (종료 메서드 호출)
prototype 스코프 #
[요약]
1. PreDestroy 와 같은 종료 콜백 메서드 실행 X
2. 매번 새롭게 Bean 생성
빈을 요청하면 항상 새로운 Bean(인스턴스)를 생성/반환
- 싱글톤 => 스프링 컨테이너 띄워질 때 생성해놓고 관리
- 프로토타입 => 조회/요청 시에 생성/반환
- Bean 생성 -> 반환 -> 초기화 -> 끝 (더 이상 관리하지 않음)
관리의 주체 : Bean 을 받은 클라이언트 (싱글톤의 경우 -> 스프링 컨테이너)
- 따라서, PreDestory 같은 종료 메서드 실행되지 않음
실무에서 거의 사용되지 않는다고함
싱글톤 빈과 함께 사용할 때 주의할 점
@Scope("singleton")
class ClientBean {
private final PrototypeBean prototypeBean;
}
@Scope("prototype")
class PrototypeBean {
}
- ClientBean 생성 / DI 시점에 PrototypeBean 이 프로토타입 형태로 주입 받음
- 이후에도 ClientBean 은 PrototypeBean 을 가지고 있고, 다시 주입받을 일이 없음. (요청할 일이 없음)
- 결론 => PrototypeBean 싱글톤 느낌으로 동작하게 됨.
- prototype 의도한대로 하려면 아래와 같이 사용해야함
// 방법 1 : ApplicationContext 사용
// - 코드가 너무 지저분해짐
// - 단위 테스트 어렵고, 스프링 컨테이너(ApplicationContext)에 의존
@Scope("singleton")
class ClientBean {
private final ApplicationContext applicationContext;
void add() {
// DL(Dependency LookUp) : 의존관계를 직접 조회/찾음 (<-> DI)
PrototypeBean prototypeBean = applicationContext.getBean(PrototypeBean.class);
prototypeBean.addCount();
...
}
}
// 방법 2 : ObjectProvider 사용 (springframework 제공)
// 위에서 DL 역할만 있으면 됨.
@Scope("singleton")
class ClientBean {
private final ObjectProvider<PrototypeBean> prototypeBeanProvider;
void add() {
// DL(Dependency LookUp) : 의존관계를 직접 조회/찾음 (<-> DI)
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
...
}
}
// 방법 3 : Provider 사용 (Java 표준, 의존성 추가 : javax.inject:javax.inject:1)
@Scope("singleton")
class ClientBean {
private final Provider<PrototypeBean> prototypeBeanProvider;
void add() {
// DL(Dependency LookUp) : 의존관계를 직접 조회/찾음 (<-> DI)
PrototypeBean prototypeBean = prototypeBeanProvider.get();
prototypeBean.addCount();
...
}
}
(주의할 점) ObjectProvider 는
- prototype 을 위해 만들어진게 아님
- ApplicationContext LookUp 을 위해 사용하는 것
- 단, 스프링에 의존
(주의할 점) Provider 는
- java 표준
- 의존성 추가
request 스코프 (웹 스코프) #
- HTTP 요청 당 유지되는 스코프 (즉, HTTP 응답 나갈 때, 종료)
- 각각의 HTTP 요청 마다 Bean 생성/관리
e.g. 동시에 2 Client 가 요청하면, 각각 다른(다른 HTTP 요청이기이) 빈 생성
(아마 아래 스코프도 동일할 듯) 아래와 같은 코드는 오류 발생
이유 :
- MyService 는 웹 애플리케이션이 뜰 때 Bean 으로 등록된다.
- 이때, MyLog 를 주입받아야 하는데, MyLog 는 HTTP 요청이 없으니 Bean 으로 관리 X
@Service
class MyService {
private final MyLog myLog;
public void logic() {
...
}
}
@Scope("request")
class MyLog {
...
}
방법 1 : Provider
DL 은 여러 번 호출 되어도, 같은 생명주기 내에서는 동일한 Bean 보장
Controller, Service 에서 myLogProvider.getObject() 호출 -> 동일한 Bean
// 방법 1 : Provider 사용
@Service
class MyService {
private final ObjectProvider<MyLog> myLogProvider;
public void logic() {
// DL 은 여러 번 호출 되어도, 같은 생명주기 내에서는 동일한 Bean 보장
// e.g. Controller, Service 에서 myLogProvider.getObject() 호출 -> 동일한 Bean
MyLog myLog = myLogProvider.getObject();
...
}
}
@Scope("request")
class MyLog {
...
}
방법 2 : 프록시
싱글톤 빈을 띄울 때, 프록시 객체를 주입
실제 동작 => 프록시 메서드 내에서 DL 을 사용하거나 해서 동작할 것으로 예상
proxyMode = ScopedProxyMode.TARGET_CLASS
- 적용 대상 인터페이스 : INTERFACES 선택
- 적용 대상 인터페이스 X : TARGET_CLASS 선택
@Service
class MyService {
private final MyLog myLog;
public void logic() {
...
}
}
@Scope(
value = "request",
proxyMode = ScopedProxyMode.TARGET_CLASS
)
public class MyLog {
}
:star::star: Provider, 프록시 의 핵심 아이디어 : 지연 처리 (꼭 필요한 시점) (다형성 + DI 의 가장 큰 강점)
session 스코프 #
- HTTP Session 당 유지되는 스코프
application 스코프 #
- 서블릿컨텍스트(ServletContext) 와 동일한 생명 주기
websocket #
- 웹 소켓과 동일한 생명 주기
알아둘것 :star::star::star: #
프록시 개념 : 스프링의 핵심 개념
- 클라이언트(개발자)의 코드 수정 없이 다양한 기능을 부여/조작할 수 있음