Spring

스프링 핵심 원리 - 기본편(빈 스코프)

코드파고 2022. 4. 28. 01:03

스코프 : 빈이 존재할 수 있는 범위를 뜻한다

스프링의 스코프 종류

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 {