목차
서론
전에 작성했던 글에서 싱글톤 빈과 프로토타입 스코프 빈을 함께 썼을 때, 프로토타입 빈이 제대로 동작하지 않는 문제점에 관해 작성했습니다. 이에 대한 해결책으로 싱글톤 빈이 프로토타입을 주입받는 대신 스프링 컨테이너 전체를 주입받아 해결했는데, 이 방법 또한 스프링 컨테이너에 종속적인 코드가 되고 단위테스트가 어려워진다는 문제점이 있습니다.
오늘 작성할 ObjectProvider와 JSR-330 Provider를 통해 이 문제를 해결할 수 있습니다.
지정한 프로토타입을 필요한 시점에 스프링 컨테이너에서 대신 찾아주는, 즉, 의존관계를 주입받는게 아니라 직접 찾아 쓰는 의존관계 탐색 (DL, Dependency Lookup)의 기능을 수행하는 역할을 합니다.
(아래의 예제는 전에 작성한 글의 예제와 이어집니다.)
ObjectFactory, ObjectProvider
지정한 빈을 요청 시점(getObject())에 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것이 ObjectProvider입니다.
참고로 과거에는 ObjectFactory가 있었는데, 여기에 편의 기능을 추가해서 ObjectProvider가 만들어졌습니다.
@Scope(value="singleton")
static class ClientBean {
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanObjectProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanObjectProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
- 실행하면 prototypeBeanObjectFactory.getObject()를 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있습니다.
- ObjectProvider의 getObject()를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아 반환합니다. (DL)
- 스프링이 제공하는 기능을 사용하지만, 기능이 단순하므로 단위 테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워집니다.
- ObjectProvider는 딱 필요한 DL정도의 기능만 제공합니다.
ObjectFactory vs ObjectProvider
- ObjectFactory: 기능이 단순합니다. 별도의 라이브러리가 필요 없으며, 스프링에 의존합니다.
- ObjectProvider: 상속, 옵션, 스트림 처리 등의 편의 기능이 많고, 별도의 라이브러리가 필요 없으며, 스프링에 의존합니다.
JSR-330 Provider
ObjectProvider와 같은 기능을 제공하지만, javax.inject.Provider라는 JSR-330 자바 표준을 사용하는 방법입니다.
(스프링부트 3.0은 jakarta.inject.Provider 를 사용합니다.)
이 방법을 사용하려면 우선 라이브러리를 gradle에 추가해야 합니다.
라이브러리 추가(build.gradle)
implementation 'javax.inject:javax.inject:1' //스프링 부트 3.0 미만
implementation 'jakarta.inject:jakarta.inject-api:2.0.1' //스프링 부트 3.0 이상
예제
@Scope(value="singleton")
static class ClientBean {
@Autowired
private Provider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
- 실행해보면 provider.get()을 통해 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있습니다.
- provider의 get()을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아 반환합니다. (DL)
- 자바 표준이고, 기능이 단순하므로 단위 테스트를 만들거나 mock 코드를 만들기 훨씬 쉬워집니다.
- Provider는 지금 딱 필요한 DL 정도의 기능만을 제공합니다.
javax.inject.Provider 참고용 소스
package javax.inject;
public interface Provider<T> {
T get();
}
특징
- get() 메서드 하나로 기능이 매우 단순합니다.
- 별도의 라이브러리가 필요합니다.
- 자바표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있습니다.
정리
- 그러면 프로토타입 빈은 언제 사용할까?
매번 사용할 때마다 의존관계 주입이 완료된 새로운 객체가 필요하면 사용하면 됩니다. 그런데, 실무에서 웹 애플리케이션을 개발해보면, 싱글톤 빈으로 대부분의 문제를 해결할 수 있기 때문에 프로토타입 빈을 직접적으로 사용하는 일은 매우 드뭅니다. - ObjectProvider, JSR330 Provider 등은 프로토타입에서만 사용하는 것이 아니라 DL이 필요한 경우는 언제든지 사용할 수 있습니다.
ex) 사용예제
- lazy(지연) or optional
- breaking circular dependencies (A→B 의존, B→A의존) : B가 A를 필요로 하는 시점을 실제 사용하는 시점으로 생성을 미룰 수 있기 때문에 순환 참조 문제가 발생하지 않습니다.
- retrieving multiple instance (프로토타입 인스턴스) <현재 예제>
참고
스프링이 제공하는 메서드에 @Lookup 애노테이션을 사용하는 방법도 있지만, 두 방법들로도 충분하고 고려해야할 내용이 많아서 생략하겠습니다.
참고
실무에서 자바 표준인 JSR-330 Provider를 사용할 것인지, 아니면 스프링이 제공하는 ObjectProvider를 사용할 것인지 고민이 될 것입니다.
ObjectProvider는 DL을 위한 편의 기능을 많이 제공해주고, 스프링 외의 별도의 의존관계 추가도 필요가 없기 때문에 편리합니다. 만약 (정말 그럴일은 없겠지만) 코드를 스프링이 아닌 다른 컨테이너에서 사용한다면 JSR-330 Provider를 사용해야 합니다.
스프링을 사용하다보면 자바 표준과 스프링이 제공하는 기능이 겹칠 때가 많습니다.
대부분 스프링이 더 다양하고 편리한 기능을 제공해주기 때문에, 특별히 다른 컨테이너를 사용할 일이 없다면 스프링이 제공하는 기능을 사용하면 됩니다.
출처
'Backend > Spring | SpringBoot' 카테고리의 다른 글
[Spring] 스프링은 왜 만들었나요? 스프링과 객체지향 (0) | 2023.07.20 |
---|---|
[SpringBoot] 웹 스코프 (Request 스코프) (0) | 2023.07.10 |
[SpringBoot] 빈 스코프 (싱글톤과 프로토타입 스코프) (0) | 2023.07.09 |
[SpringBoot] 초기화 및 소멸전 콜백 메서드 (0) | 2023.07.02 |
[SpringBoot] @Autowired 매칭한 빈이 2개 이상일 때 해결방안 (0) | 2023.06.24 |