본문 바로가기

back-end/spring

spring/ 빈 스코프

 

빈 스코프 Bean Scope

빈이 생성되어 종료될 때까지의 빈이 존재할 수 있는 범위를 말한다. 

 

빈 스코프 지정하기

@ComponentScan을 사용하는 경우

@Scope("prototype")
@Component
public class ProtoScope {...}

 

수동 등록하는 경우

@Scope("prototype")
@Bean
prototypeBean Proto() {...}

 


 

1. 싱글톤 스코프

스프링 컨테이너 생성 시점에 싱글톤 빈이 생성되고 초기화 메서드가 실행된다. 클라이언트의 요청 시 스프링 컨테이너 생성 시점에 이미 생성되어 있는 스프링 빈을 반환한다.

 

@Scope 값을 설정하지 않을 시에 설정되는 기본값으로 스프링 컨테이너의 시작과 종료까지 유지된다.

 


 

2. 프로토타입 스코프

클라이언트의 요청이 올 때 스프링 컨테이너가 프로토타입 빈을 생성하고 의존 관계를 주입한다.

 

싱글톤 스코프를 가진 빈이 클라이언트에 의해 요청될 때 스프링 컨테이너는 이미 생성해놓은 스프링 빈을 반환한다. 

 

반면, 클라이언트 측에서 프로토타입 스코프를 가진 빈을 스프링 컨테이너에 요청할 때 스프링 컨테이너는 프로토타입 빈을 생성하고 의존 관계를 주입한다. 

또한 프로토타입 빈은 클라이언트에게 생성한 빈을 반환한 후에는 빈에 관여하지 않기 때문에 종료 메서드가 호출되지 않는다. 즉, 스프링 컨테이너는 프로토타입 빈의 생성과 의존 관계 주입, 초기화까지만을 담당한다.

 

요약

 

  • 스프링 컨테이너에 요청이 올 때마다 빈이 새로 생성
  • 스프링 컨테이너는 프로토타입 빈의 생성, 의존 관계 주입, 초기화까지만 담당
  • 종료 메서드가 호출되지 않음 → 종료 메서드 호출을 클라이언트가 직접 해야 한다

 

싱글톤 스코프 빈에서 프로토타입 빈을 사용하는 경우

싱글톤 빈은 스프링 컨테이너 생성 시에 단 하나만 생성되고 의존 관계가 주입된다. 따라서 싱글톤 빈 내부의 프로토타입 빈 역시 의존 관계가 다시 주입되지 않고 처음에 생성된 프로토타입 빈이 계속 호출된다. 이는 프로토타입 빈을 사용하는 올바른 방식이 아니다.

 

ObjectProvider 사용하기

@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;

public int logic() {
    //항상 새로운 프로토타입 빈을 반환한다
    PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
    ...
}

 

 


 

3. 웹 스코프

웹 환경에서만 동작하며 스프링이 스코프의 종료 시점까지 관리한다. 즉, 스프링에 의해 종료 메서드가 호출된다.

 

  • request 스코프
    • HTTP 요청 하나가 들어오고 나갈 때까지 유지된다. 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성, 관리된다.
  • session 스코프
    • HTTP session과 동일한 생명주기를 가진다.
  • application 스코프
    • ServletContext와 동일한 생명주기를 가진다.
  • websocket 스코프
    • web socket과 동일한 생명주기를 가진다.

 

request 스코프

@Component
@Scope("request")
public class Logger {...}

 

@Controller
@RequiredArgsConstructor
public class DemoController {
    private final DemoService demoService;
    private final Logger logger;
    
    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServeltRequest request) {...}
}

 

@Service
@RequiredArgsConstructor
public class DemoService {
    private final Logger logger;
    
    public void logic(String id) {...}
}

 

위의 코드는 실제로 사용될 수 없다. 스프링 컨테이너에서 빈을 등록할 때 의존 관계 주입이 이루어져야 하는데 클라이언트의 요청이 오지 않았기 때문에 의존 관계를 주입할 수 없기 때문이다.

 

request 스코프로 지정된 빈은 HTTP 요청 당 하나씩 생성되고 HTTP 요청이 끝나는 시점에 소멸된다는 것에 주목한다. 스프링 애플리케이션을 실행할 때, HTTP 요청이 오지 않았다면 request 스코프로 지정된 빈은 생성될 수 없다. 따라서 스프링 컨테이너가 생성되고 의존 관계가 주입되는 시점이 아닌 실제 요청이 올 때까지 빈의 생성을 지연시키는 과정이 필요하다.

 


 

ObjectProvider 사용하기

  • ObjectProvider.getObject()가 호출되는 시점까지 request 스코프 빈의 생성을 지연시킨다
  • ObjectProvider.getObject()가 호출되는 시점에는 HTTP 요청이 진행 중이므로 request 스코프 빈의 생성이 가능하다

 

@Component
@Scope("request")
public class Logger {...}

 

@Controller
@RequiredArgsConstructor
public class DemoController {
    private final DemoService demoService;
    //ObjectProvider 사용
    private final ObjectProvider<Logger> loggerProvider;
    
    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServeltRequest request) {...}
}

 

@Service
@RequiredArgsConstructor
public class DemoService {
    //ObjectProvider 사용
    private final ObjectProvider<Logger> loggerProvider;
    
    public void logic(String id) {
    //ObjectProvider를 통해 getObject() 사용
    Logger logger = loggerProvider.getObject();
    ...
    }
}

 


proxyMode 사용하기

 

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

 

proxyMode = ScopedProxyMode.TARGET_CLASS의 추가로 Logger의 프록시 클래스를 만들어두고, HTTP 요청 여부에 관계없이 프록시 클래스를 만들어뒀으므로 빈에 미리 주입해 놓는 것이 가능하다.

 

즉, CGLIB 바이트 코드 조작 라이브러리로 Logger를 상속받은 가짜 프록시 객체를 생성하고 스프링 빈으로 등록한다. 이후 실제 객체의 조회는 HTTP 요청이 올 때까지 지연되다가 HTTP 요청이 오는 순간 프록시 객체 내부의 실제 빈을 요청하는 위임 로직이 실행된다. 

 

@Controller
@RequiredArgsConstructor
public class DemoController {
    private final DemoService demoService;
    private final Logger logger;
    
    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServeltRequest request) {...}
}

 

@Service
@RequiredArgsConstructor
public class DemoService {
    private final Logger logger;
    
    public void logic(String id) {...}
}

 

따라서 프록시의 사용으로 인해 위에서 사용 불가능했던 코드를 변경 없이 그대로 사용할 수 있다.