싱글톤 빈인 ClientBean이 PrototypeBean을 사용(문제)
- 스프링 컨테이너 생성 시점에 ClientBean 빈 생성 후 clientBean에 의존 관계 주입
- PrototypeBean 빈 생성 후 prototypeBean을 clientBean에 반환(prototypeBean의 count = 0)
- clientBean의 logic() 호출
- prototypeBean의 addCount() 호출
- count가 1 늘어남(prototypeBean의 count = 1)
- ClientBean 빈 생성 시점에 이미 PrototypeBean이 지정 되기 때문에 클라이언트가 2명이여도 결국은 같은 PrototypeBean을 가지게 된다
- 따라서 의존성 주입을 따로하고나서 logic()을 호출하여도 같은 빈을 반환받기 때문에 값은 2가 된다
(prototypeBean의 count = 2)
package hello.core.scope;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
public class SingletonWithPrototypeTest1 {
@Test
void singletonClientUsePrototype() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
ClientBean clientBean1 = ac.getBean(ClientBean.class);
int count1 = clientBean1.logic();
Assertions.assertThat(count1).isEqualTo(1);
ClientBean clientBean2 = ac.getBean(ClientBean.class);
int count2 = clientBean2.logic();
Assertions.assertThat(count2).isEqualTo(2);
}
@Scope("singleton")
static class ClientBean {
private final PrototypeBean prototypeBean;
@Autowired
public ClientBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public int logic() {
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
@Configuration
@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("init");
}
@PreDestroy
public void destroy() {
System.out.println("destroy");
}
}
}
싱글톤 빈인 ClientBean이 PrototypeBean을 사용(해결) - DL
- 새로운 PrototypeBean을 받고 싶은 경우 의존관계를 외부에서 주입(DI) 받는게 아니라 이렇게 직접 필요한 의존관계를 찾으면 되는데 이것을 Dependency Lookup (DL) 의존관계 조회(탐색) 이라한다
- 의존관계주입은 스프링컨테이너 생성 시점에 주입을 하고 의존관계조회는 원하는 시점에 주입을 하는 것
- 예시
@Autowired
private ApplicationContext ac;
public int logic() {
PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
- 그러나 위 방법은 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트도 어려워진다.
싱글톤 빈인 ClientBean이 PrototypeBean을 사용(해결) - ObjectProvider
- 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것
- 예시
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
- 스프링이 제공하는 기능을 사용하지만, 기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워진다.
- 그러나 여전히 스프링에 의존적임
싱글톤 빈인 ClientBean이 PrototypeBean을 사용(해결) - Provider
- 똑같은 DL 서비를 제공하는 JSR-330 자바 표준을 사용하는 방법이다
- 보통 이 방법을 사용함(떄에 따라 다름)
- 스프링부트 3.0 미만
javax.inject:javax.inject:1
라이브러리를 gradle에 추가해야 한다.
- 스프링부트 3.0 이상
jakarta.inject:jakarta.inject-api:2.0.1
라이브러리를 gradle에 추가해야 한다.
- 예시
@Autowired
private Provider<PrototypeBean> provider;
public int logic() {
PrototypeBean prototypeBean = provider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
- 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있고, 기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워진다.