728x90
반응형
섹션9 빈 스코프
프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점
- 스프링 컨테이너에 프로토타입 스코프의 빈을 요청하면 항상 새로운 객체 인스턴스를 생성해서 반환한다. 하지만 싱글톤 빈과 함께 사용할 때는 의도한대로 잘 동작하지 않으므로 주의해야 한다.
- 프로토타입 빈 직접 요청
- 스프링 컨테이너에 프로토타입 빈 직접 요청1
- 클라이언트A는 스프링 컨테이너에 프로토타입 빈을 요청한다
- 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x01)한다 해당 빈의 count 필드 값은 0이다.
- 클라이언트는 조회한 프로토타입 빈에 addCount()를 호출하면서 count 필드를 +1한다.
- 결과적으로 프로토타입 빈(x01)의 count는 1이 된다
- 스프링 컨테이너에 프로토타입 빈 직접 요청2
- 클라이언트A는 스프링 컨테이너에 프로토타입 빈을 요청한다
- 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x02)한다 해당 빈의 count 필드 값은 0이다.
- 클라이언트는 조회한 프로토타입 빈에 addCount()를 호출하면서 count 필드를 +1한다.
- 결과적으로 프로토타입 빈(x02)의 count는 1이 된다
- 스프링 컨테이너에 프로토타입 빈 직접 요청1
- 싱글톤 빈에서 프로토타입 빈 사용: 이번에는 clientBean이라는 싱글톤 빈이 의존관계 주입을 통해서 프로토타입 빈을 주입받아서 사용하는 예를 보자
- 싱글톤에서 프로토타입 빈 사용1
- clientBean은 싱글톤이므로, 보통 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 주입도 발생한다
- clientBean은 의존관계 자동 주입을 사용한다. 주입 시점에 스프링 컨테이너에 프로토타입 빈을 요청한다
- 스프링 컨테이너는 프로토타입 빈을 생성해서 clientBean에 반환한다. 프로토타입 빈의 count 필드 값은 0이다.
- 이제 clientBean은 프로토타입 빈의 참조값을 내부 필드에 보관한다.
- 싱글톤에서 프로토타입 빈 사용2
- 클라이언트A는 clientBean을 스프링 컨테이너에 요청해서 받는다. 싱글톤이므로 항상 같은 clientBean이 반환된다
- 클라이언트A는 clientBean.logic을 호출한다
- clientBean은 prototypeBean의 addCount()를 호출해서 프로토타입 빈의 count를 증가한다. count값이 1이 된다
- 싱글톤에서 프로토타입 빈 사용3
- 클라이언트B는 clientBean을 스프링 컨테어니에 요청해서 받는다. 싱글톤이므로 항상 같은 clientBean이 반환된다
- 여기서 clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이다. 중비 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성이 된 것이지, 사용할 때마다 새로 생성되는 것이 아니다
- 클라이언트B는 clientBean.logic을 호출한다
- clientBean은 prototypeBean의 addCount()를 호출해서 프로토타입 빈의 count를 증가한다. 원래 count 값이 1이었으므로 2가 된다.
- 싱글톤에서 프로토타입 빈 사용1
- 스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 된다. 그런데 싱글톤 빈은 생성 시점에만 의존관계 주입을 받기 떄문에 프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과 함께 계속 유지된는 것이 문제다. 그러니까 프로토타입 빈을 주입 시점에만 새로 생성하는 것이 아니라 사용할 때마다 새로 생성해야 한다
프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결
- 싱글톤 빈과 프로토타입 빈을 함께 사용할 떄 어떻게 하면 사용할 때마다 항상 새로운 프로토타입 빈을 지저분하지 않게 생성할 수 있을까?
- 의존관계를 외부에서 주입받는게 아니라 직접 필요한 의존관계를 찾는 것을 의존관계 조회(DL, Dependency Lookup)라고 한다 지금 필요한 기능은 프로토타입 빈을 컨테이너에서 대신 찾아주는 DL정도의 기능이다. 아래 두가지 방법이 제공된다
- ObjectFactory, ObjectProvider
- DL 서비스를 제공하는 인터페이스들이다. 과거에는 ObjectFactory가 있었는데 여기에 몇가지 편의 기능을 추가해 ObjectProvider가 만들어졌다
static class ClientBean { @Autowired private ObjectProvider<PrototypeBean> prototypeBeanProvider; public int logic() { PrototypeBean prototypeBean = prototypeBeanProvider.getObject(); prototypeBean.addCount(); return prototypeBean.getCount(); } }
- prototypeBeanProvider.getObject()를 통해서 항상 새로운 프로토타입 빈이 생성된다
- ObjectProvider의 getObject()를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다(DL)
- 스프링이 제공하는 기능을 사용하지만 기능이 단순하므로 단위 테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워진다
- DL 서비스를 제공하는 인터페이스들이다. 과거에는 ObjectFactory가 있었는데 여기에 몇가지 편의 기능을 추가해 ObjectProvider가 만들어졌다
- javax.inject.Provider(JSR-330)
- 이 방법을 사용하려면 해당 라이브러리를 build.gradle에 추가해야 한다
depedencies { ... implementation 'javax.inject:javax.inject:1' ... }
- 코드는 아래처럼 작성한다
static class ClientBean { @Autowired private Provider<PrototypeBean> prototypeBeanProvider; public int logic() { PrototypeBean prototypeBean = prototypeBeanProvider.get(); prototypeBean.addCount(); return prototypeBean.getCount(); } }
- prototypeBeanProvider.get을 통해 새로운 프로토타입 빈이 생성됨을 확인할 수 있다.
- Provider의 get을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다(DL)
- 자바 표준이고 기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만들기가 훨씬 쉬워진다
- 별도의 라이브러리를 받아야하는게 단점이지만 자바표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.
- 이 방법을 사용하려면 해당 라이브러리를 build.gradle에 추가해야 한다
- ObjectFactory, ObjectProvider
- 프로토타입 빈은 매번 사용할 때마다 의존관계 주입이 완료된 새로운 객체가 필요할 때 사용한다 실무에서는 보통 싱글톤빈으로 대부분의 문제를 해결할 수 있기 때문에 프로토타입 빈을 직접적으로 사용하능 일은 매우 드물다
- ObjectProvider, javax.inject.Provider 등은 프로토타입 뿐만 아니라 DL이 필요한 경우라면 언제든지 사용할 수 있다.
웹 스코프
- 웹 스코프는 웹 환경에서만 동작한다. 프로토타입과 다르게 스프링이 해당 스코프의 종료 시점까지 관리한다. 따라서 종료 메서드가 호출된다
- 웹 스코프 종류
- request: HTTP 요청 하나가 들어오고 나갈 때까지 유지되는 스코프. 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다
- session: HTTPSession과 동일한 생명주기를 가지는 스코프
- application: 서블릿 컨텍스트와 동일한 생명주기를 가지는 스코프
- websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프
request 스코프 예제 만들기
- 웹 스코프는 웹 환경에서만 동작하기 때문에 build.gradle에 라이브러리를 추가해야 한다.
dependencies { ... implementation 'org.springframework.boot:spring-boot-starter-web' ... }
- @SpringBootApplication 어노테이션이 붙은 클래스에서 main메서드를 실행시킨 뒤 웹 브라우저에 http://localhost:8080을 입력하면 에러 페이지가 나타남을 확인할 수 있다. 이렇게 돼야 정상 실행된 것이다.
- 동시에 여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵다. 이럴때 request 스코프를 사용하면 좋다 @Scope(value = "request")로 지정한다
- 예제 실행 시 에러가 발생하는데 이는 스프링 컨테이너가 뜰 때 @Controller 클래스가 빈으로 등록하고 의존관계 주입이 발생한다. 컨트롤러는 스프링 컨테이너한테 MyLogger를 달라고 하지만 MyLogger의 스코프는 request이다. 스프링이 MyLogger를 내놓으려고 하지만 request가 없어서 예외가 발생하는것. request의 생존 범위는 고객이 들어와서 나갈 때까지인데 고객의 진입 자체가 없었기 때문에 즉 request 자체가 없기 때문에 @Scope("request")가 활성화되지 않아서 예외가 발생한 것이다. 이 문제를 고치려면 Provider를 사용하면 된다
스코프와 Provider
- Provider 이용해 실습
스코프와 프록시
- 대상 클래스에 @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)를 지정한다. 만약 적용 대상이 인터페이스라면 proxyMode를 ScopedProxyMode.INTERFACES를 선택한다 이렇게 하면 대상 클래스의 가짜 프록시 클래스를 만들어두고 HTTP request와 관계 없이 가짜 프록시 클래스를 다른 빈에 미리 주입한다
섹션10 다음으로
다음으로
-
반응형
728x90
반응형