본문 바로가기
Backend, Server/Spring

[스프링] 빈 스코프(싱글톤, 프로토타입, 웹 관련 스코프)

by ggyongi 2022. 1. 1.
반응형

스코프: 빈이 존재할 수 있는 범위

 

스프링이 지원하는 다양한 스코프

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

프로토타입: 사용법은 빈에 @Scope("prototype")을 설정해주면 된다.

프로토타입 빈은 컨테이너에게 빈을 요청할 때마다 매번 새로운 객체를 생성하여 반환해준다.

스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프, 싱글톤이 아니기때문에 요청마다 매번 다른 인스턴스를 반환, 초기화까지만 관여하기 때문에 @PreDestroy같은 종료 메서드가 실행되지 않음

웹 관련 스코프

 - request : 웹 요청이 들어오고 나갈때 까지 유지되는 스코프

 - session : 웹 세션이 생성되고 종료될 때까지 유지되는 스코프

 - application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

 

프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점

싱글톤 빈에서 프로토타입 빈 사용

clientBean 이라는 싱글톤 빈이 의존관계 주입을 통해서 프로토타입 빈을 주입받아서 사용하는 예를 보자.

여기서 중요한 점이 있는데, clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이다. 프로토타입 스코프는 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈을 생성하는 것이지, 사용 할 때마다 새로 생성하는 것이 아님!

클라이언트 B가 logic을 호출하면 프로토타입 빈을 사용했음에도 불구하고 원래의 의도와는 달리 count가 1에서 2로 증가함. 다시말해서 클라이언트 B에게 새로운 프로토타입 빈이 전달되는 것이 아니라 기존의 프로토타입 빈이 전달됨.

우리가 원하는 것이, 즉 프로토타입 빈을 사용하는 목적이 이런 것은 아닐 것이다. 프로토타입 빈을 주입 시점에만 새로 생성하는게 아니라, 사용할 때 마다 새로 생성해서 사용하는 것을 원할 것이다.

 

해결법은 매 사용마다 ac.getBean()을 통해 계속 새로 전달받으면 된다.

logic을 아래와 같이 수정

public int logic() {
    PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class); //새로운 객체 반환됨
    prototypeBean.addCount();
    int count = prototypeBean.getCount();
    return count;
 }

근데 위 해결법의 문제점은 ac를 주입받아야 하기 때문에 컨테이너에 종속적인 코드가 됨. 그러면 테스트도 어려워짐다. 그래서 아래와 같이 해결하는 방법도 있음

 

 

프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결

- Provider 사용하기 -> get()을 통해 항상 새로운 프로토타입 빈을 받을 수 있게 된다.

//implementation 'javax.inject:javax.inject:1' gradle 추가 필수
@Autowired
private Provider<PrototypeBean> provider;

public int logic() {
    PrototypeBean prototypeBean = provider.get();
    prototypeBean.addCount();
    int count = prototypeBean.getCount();
    return count;
}

 

 

 

웹 스코프 - request 스코프

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

 

request 스코프 사용 시 발생할 수 있는 오류 상황

Error creating bean with name 'myLogger': Scope 'request' is not active for the 
current thread; consider defining a scoped proxy for this bean if you intend to 
refer to it from a singleton;

리케이션을 실행하는 시점에 싱글톤 빈은 생성해서 주입이 가능하지만, request 스코프 빈은 아직 생성되지 않기 때문이다. 이 빈은 실제 고객의 요청이 와야 생성할 수 있다.

 

해결법 1. Provider 사용 - 아래 예시에선 ObjectProvider 사용

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final LogDemoService logDemoService;
    private final ObjectProvider<MyLogger> myLoggerProvider;
    
    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
       String requestURL = request.getRequestURL().toString();
       MyLogger myLogger = myLoggerProvider.getObject();
       myLogger.setRequestURL(requestURL);
       myLogger.log("controller test");
       logDemoService.logic("testId");
       return "OK";
    }
}

 

해결법 2. 프록시 사용

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

프록시는 이전에 배웠던 방식과 같이,

CGLIB이라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입하고,

실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있음.

 

 

 

 

 

------------------------

참고 : 인프런 김영한님 강의(스프링 핵심원리 기본편)

 

비전공자 네카라 신입 취업 노하우

시행착오 끝에 얻어낸 취업 노하우가 모두 담긴 전자책!

kmong.com

댓글