[Spring 기본] 8. 빈 스코프

heyhey·2023년 2월 6일
0

Spring

목록 보기
17/23

스프링 빈은 기본적으로 싱글톤 스코프라고 지금껏 배워왔다.
스코프는 빈이 존재할 수 있는 범위를 뜻한다.

스프링은 싱글톤 스코프를 포함해 여러 스코프를 지원한다.

  • 싱글톤 스코프 : 기본 스코프로, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.

  • 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다.

  • 웹 관련 스코프 :

    • request : 웹 요청이 들어오고 나갈때 까지 유지되는 스코프
      => 요청이 얼마나 걸리는지 시간을 재는 기능에 유용
    • session : 웹 세션이 생성되고 종료될 때까지 유지되는 스코프
    • application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

프로토타입 스코프

싱글톤 스코프와 달리 항상 새로운 인스턴스를 생성해서 반환한다.

순서

  1. 프로토타입 스코프의 빈을 스프링 컨테이너에 요청한다
  2. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입받는다.
  3. 생성한 프로토타입 빈을 클라이언트에게 반환한다.
  4. 항상 새로운 프로토타입 빈을 생성해서 반환하게 된다.
  5. (생성된 프로토타입 빈을 관리하지 않는다. )

생성하기

@Scope("prototype")
static class SingletonBean {
    @PostConstruct
    public void init() {
        System.out.println("SingletonBean.init");
    }
    @PreDestroy
    public void destroy() {
        System.out.println("SingletonBean.destroy");
    }
}
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class); 
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class); 
System.out.println("prototypeBean1 = " + prototypeBean1); 
System.out.println("prototypeBean2 = " + prototypeBean2);

=> 서로 다른 인스턴스를 가진 빈이 두개가 출력되는 것을 볼 수 있다.
스프링 컨테이너가 생성과 의존관계 주입까지만 실행되기 때문에 종료 메서드는 실행되지 않는다.
프로토타입 빈은 프로토타입 빈을 조회한 클라이언트가 관리해야 한다

싱글톤과 프로토타입의 공존

문제점

싱글톤 빈과 함께 사용하면 의도한대로 잘 동작하지 않는다.
싱글톤 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이기 떄문에, 조회를 할 경우 빈이 새로 생기지 않는다.

싱글톤 빈은 생성 시점에만 의존관계 주입을 받기 때문에, 프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과 함께 계속 유지된다.

Provider

싱글톤 빈과 프로토타입을 함께 사용할 때, 항상 새로운 프로토타입 빈을 생성하는 방법
싱글톤 빈이 프로토타입을 사용할 때마다 스프링 컨테이너에 새롭게 요청을 한다.

의존관계를 외부에서 주입 받는 것이 아니라 의존관계를 찾는 것을 Dependency Lookup 의존관계 조회라고 한다.

public int logic() {
    PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
}

항상 새로운 프로토타입 빈을 생성해서 조회를 하게 된다.

스프링에서는 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것이 있다.

ObjectProvider

public int logic() {
    PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
}

실행해보면 prototypeBeanProvider.getObject() 을 통해서 항상 새로운 프로토타입 빈이 생성된다.


웹스코프

웹 스코프는 웹 환경에서만 동작한다.
프로토타입과는 달리 스프링이 해당 스코프의 종료시점까지 관리한다.

  • request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다.

  • session: HTTP Session과 동일한 생명주기를 가지는 스코프

  • application: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프

  • websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프

request 스코프

동시에 여러 Http 요청이 오게 되면 어떤 요청이 남긴 로그인지 알기가 어렵다.
이럴 때 사용하는 것이 Request 스코프이다.

웹환경에서만 동작하기 때문에 spring-boot-starter-web 라이브러리를 추가해준다.
implementation 'org.springframework.boot:spring-boot-starter-web'
이제 스프링 부트는 내장 톰켓 서버를 활용해서 웹 서버와 스프링을 함께 실행시킨다.

@Component
@Scope(value = "request")
public class MyLogger {
    private String uuid;
    private String requestURL;

    public String getRequestURL() {
        return requestURL;
    }

    public void setRequestURL(String requestURL) {
        this.requestURL = requestURL;
    }
    public  void log(String message){
        System.out.println("[" + uuid + "]" + "[" + requestURL + "]" + message);
    }

    @PostConstruct
    public void init(){
        uuid = UUID.randomUUID().toString();
        System.out.println("[" + uuid + "] Request scope been create" + this);
    }
    @PreDestroy
    public void close(){
        System.out.println("[" + uuid + "] Request scope been closed" + this);
    }
}

로그를 출력하기 위한 클래스이고 @Scope(value = "request")로 request 스코프로 지정했다.
이 빈은 http 요청 당 하나씩 생성이 되고, Http 요청이 끝나는 시점에 소멸이된다.

@PostConstruct 초기화 메서드를 이용해 uuid를 생성한다.
@PreDestroy 소멸 메서드를 이용해 이 빈이 소멸될 때 메시지를 남긴다.

public class LogDemoController {
    private final LogDemoService logDemoService;
    private final MyLogger myLogger;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        String requestURL = request.getRequestURL().toString();
        myLogger.setRequestURL(requestURL);
        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "OK";
} }

로거가 잘 작동하는지 확인하는 테스트용 컨트롤러이다.
http://localhost:8080/log-demo 로 통해 요청 URL을 받아서 진행한다.

@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final MyLogger myLogger;
    public void logic(String id) {
        myLogger.log("service id = " + id);
    } 
}

하지만 에러가 나게 된다... 생성을 먼저 했지만, 요청이 없어서 오류가 난다.

스코프와 프록시

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}

가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있다.

MyLogger의 순수한 클래스가 아니라 다른 어떠한 객체가 대신 등록이 되어 버린다.
의존관계 주입도 이 가짜 프록시 객체가 주입된다.

가짜 프록시 객체는 요청이 오면 그 때 진짜 빈을 요청하는 로직이 있다.

profile
주경야독

0개의 댓글