Spring 시리즈는 혼자 공부하며 기록으로 남기고, 만약 잘못 학습 한 지식이 있다면 공유하며 피드백을 받고자 작성합니다.
스프링에 대해 깊게 공부해보고자 인프런의 김영한 강사님께서 강의를 진행하시는 (스프링 핵심 원리 - 기본편) 강의를 수강하며 정리하는 글입니다.
혹여나 글을 읽으시며 잘못 설명된 부분이 있다면 지적 부탁드리겠습니다.
테스트 코드
public class SingletonWithPrototypeTest1 {
@Test
void singletonClientUsePrototype() {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
ClientBean clientBean1 = ac.getBean(ClientBean.class);
int count1 = clientBean1.logic();
assertThat(count1).isEqualTo(1);
ClientBean clientBean2 = ac.getBean(ClientBean.class);
int count2 = clientBean2.logic();
assertThat(count2).isEqualTo(1);
}
@Scope("singleton")
static class ClientBean {
private final ApplicationContext ac;
public ClientBean(ApplicationContext ac) {
this.ac = ac;
}
public int logic() {
PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
@Scope("prototype")
static class PrototypeBean {
private int count = 0;
public void addCount() {
count++;
}
public int getCount() {
return count;
}
@PostConstruct
public void init() {
System.out.println("PrototypeBean.init " + this);
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy");
}
}
}
실행 결과
PrototypeBean.init hello.core.scope.SingletonWithPrototypeTest1$PrototypeBean@1cf2fed4
PrototypeBean.init hello.core.scope.SingletonWithPrototypeTest1$PrototypeBean@329a1243
ApplicationContext 전체를 주입받게 되면 스프링 컨테이너에 너무 종속적이게 되고 나중에 단위 테스트를 짜기 어려워진다. @Scope("singleton")
static class ClientBean {
private final ObjectProvider<PrototypeBean> prototypeBeanProvider;
public ClientBean(ObjectProvider<PrototypeBean> prototypeBeanProvider) {
this.prototypeBeanProvider = prototypeBeanProvider;
}
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
.getObject() 메서드를 호출하면 그때서야 스프링 컨테이너에서 프로토타입 빈을 찾아서 반환해주는 것이다.ObjectProvider를 ObjectFactory 로 바꾸어도 동작한다.ObjectFactory 인터페이스
@FunctionalInterface
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
getObject() 메서드 하나만 구현되어있다.ObjectProvider 인터페이스
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {...}
ObjectProvider 인터페이스를 살펴보면 ObjectFactory를 이미 상속받고있다.ObjectFactory 에서 편의기능 몇가지를 더 추가해 만든 것이 ObjectProviderObjectFactory 상속했기 때문에 옵션이나 스트림처리 같은 몇가지 편의 기능이 많다.javax.inject.Provider 라는 JSR-330 자바 표준을 사용하는 방법스프링부트 3.0 이상
jakarta.inject:jakarta.inject-api:2.0.1 라이브러리 추가
코드 수정
@Scope("singleton")
static class ClientBean {
//gradle jakarta.inject:jakarta.inject-api:2.0.1 추가
private final Provider<PrototypeBean> prototypeBeanProvider;
public ClientBean(Provider<PrototypeBean> prototypeBeanProvider) {
this.prototypeBeanProvider = prototypeBeanProvider;
}
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.get(); //get으로 변경
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
Provider는 지금 딱 필요한 DL 정도의 기능만 제공한다.특징
get()메서드 하나로 기능이 매우 단순하다.ObjectProvider, JSR-330 Provider 같은 경우 프로토타입 뿐만아니라 DL이 필요한 경우라면 언제든지 사용가능하다.참고: 스프링이 제공하는 메서드에
@Lookup애노테이션을 사용하는 방법도 존재하지만, 이전 방법들로 충분하고 고려해야할 내용도 많아서 거의 쓰지 않는다.
참고: 그럼 ObjectProvider를 사용할지 자바표준인 JSR-330 Provider를 사용할지 고민이 될 것인데, 편한건 스프링이 제공하는 ObjectProvider이다. 정말 만약(그럴 일은 거의 없겠지만!) 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야한다면 자바 표준을 사용해야한다.