스프링 핵심 원리 - 기본편(빈 스코프)
스코프 : 빈이 존재할 수 있는 범위를 뜻한다
스프링의 스코프 종류
1. 싱글톤 : 스프링 컨테이너의 생성- 종료까지 유지
2. 프로토타입 : 프로토타입 빈의 생성과 의존관계 주입까지만 관여
3. 웹 관련 스코프:
request ) 웹 요청이 들어오고 나갈 때 까지 유지
session ) 웹 세션이 생성-종료까지 유지
application ) servlet context와 같은 범위로 유지
밑줄 친 스코프를 주로 사용한다..
[프로토타입 스코프]
싱글톤 스코프는 생성된 빈을 요청할 시 동일한 빈을 반환한다
그러나 프로토타입 스코프의 빈은 스프링 컨테이너에 프로토타입 빈을 요청한 시점에 생성하고 의존관계를 주입한다
그리고 스프링 컨테이너는 프로토타입 빈을 클라이언트에 반환하고 관리하지 않는다😅. 즉 클라이언트가 빈을 관리하게 된다. 이후에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환한다.
<정리> : 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다. 그러므로 PreDestroy가 호출되지 않는다!
싱글톤에서 프로토타입 빈을 사용 할 때는?
싱글톤 내부에서 프로토타입 빈을 참조하여 활용한다. 프로토타입 빈이 주입된 상태이다!
@Scope("prototype")
static class PrototypeBean {
@Scope("singleton")
@RequiredArgsConstructor
static class ClientBean{
private final PrototypeBean prototypeBean; // 생성시점에 주입된다
public int logic(){
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
만약 지정한 프로토타입 빈을 컨테이너에서 찾아주고 싶다면?
[ObjectFactory, ObjectProvider]
DL(Dependency Lookup) 제공. getObject()를 통해 새로운 프로토타입 빈이 생성된다.
단위 테스트, mock 코드를 만들기 쉬우나 스프링에 의존적이다
@Scope("singleton")
static class ClientBean{
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanObjectProvider;
public int logic(){
PrototypeBean prototypeBean = prototypeBeanObjectProvider.getObject();
// 스프링 컨테이너에서 필요한 빈을 찾아주는 딱 그 역할만!
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
[JSR-330 Provider]
provider.get()을 통해 새로운 프로토타입 빈이 생성된다.
별도의 라이브러리가 필요하며, 자바 표준이므로 스프링에 독립적이라 다른 컨테이너에서도 사용가능하다.
@Scope("singleton")
static class ClientBean{
@Autowired
private Provider<PrototypeBean> prototypeBeanObjectProvider;
public int logic(){
PrototypeBean prototypeBean = prototypeBeanObjectProvider.get();
// 스프링 컨테이너에서 필요한 빈을 찾아주는 딱 그 역할만!
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
Provider, ObjectProvider는 DL을 수행하므로 일반 빈을 조회할 때도 자주 사용된다.
[웹 스코프]
웹 환경에서 동작하기 때문에 웹 스코프이다😊
request ) 웹 요청이 들어오고 나갈 때 까지 유지
session ) 웹 세션이 생성-종료까지 유지
application ) servlet context와 같은 범위로 유지
Client a와 Client b가 동시에 요청하면 각각 a, b별로 다른 전용 bean이 생성된다. 그리고 request를 모두 수행하고 나가면 소멸된다.
@Component
@Scope(value = "request")
public class MyLogger {
private String uuid;
private String requestURL;
public void log(String message) {
...
}
@PostConstruct
public void init(){
...
}
@PreDestroy
public void close(){
...
}
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
private final ObjectProvider<MyLogger> myLogger;
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final ObjectProvider<MyLogger> myLoggerProvider;
public void logic(String id) {
MyLogger myLogger = this.myLoggerProvider.getObject();
myLogger.log("service id = " + id);
}
}
request 스코프의 mylogger는 HTTP 요청 당 하나씩 생성되고, HTTP 요청이 끝나는 시점에 소멸된다.
ObjectProvider로 DL을 수행 할 때 역시 request가 수행중이므로 빈이 정상적으로 등록된다.
[프록시]
proxyMode : 대상이 클래스이면 TARGET_CLASS, 인터페이스면 INTERFACES
CGLIB 라이브러리로 가짜 프록시 클래스를 생성한다. 나아가 의존관계 주입까지 프록시 개체로 등록된다. 마치 싱글톤처럼 실행된다.
그러나 가짜 프록시 개체는 실제 요청이 올 때 위임 로직을 불러 진짜 빈을 호출하게 된다. 👀 진짜 객체 조회를 지연처리 한다는 포인트가 중요 👀
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {