728x90
반응형
싱글톤과 프로토타입을 같이 사용했을때 문제점
Bean Scope Prototype: Part1 은 아래 페이지에서 확인하자.
https://vprog1215.tistory.com/71
싱글톤과 프로토 타입을 같이 사용했을때 문제점을 알아보자.
싱글톤빈 안에 프로토타입 빈이 있음.
프로토타입 빈은 count를 증가시키는 로직이 있음.
싱글톤 빈 이 프토로 타입 빈을 포함하는경우
ClientBean은 싱글톤이다 스프링컨테이너 생성시점에 함께 생성된다.
- 1. ClientBean은 의존관계 자동주입을 사용한다.
- - 주입 시점에 스프링 컨테이너에 프로토타입빈을 요청한다. (중요 포인트)
- 2. 스프링컨테이너는 프로토타입 빈을 생성해서 clientBean에 반환한다.
- - 이제 ClientBean은 프로토타입 빈을 내부 필드에 보관한다(참조값)
싱글톤 빈안에서 프로토타입의 logic 호출
싱글톤 안에서 프로토 타입의 로직을 호출한다.
1. 첫번째 호출 하면 프로토타입의 값을 1 증가시킨다.
2. 두번째 호출 하면 프로토타입의 값을 1 증가시킨다.
여기서 문제점이 발생한다.
프로토타입은 원래 한번 쓰고 컨테이너에서 관리를 하지 않는다
그렇다면 몇번을 호출 해도 값은 1이어야 한다.
하지만 값이 계속해서 증가하게 된다.
1 -> 2 이렇게 말이다. 그림으로 정확하게 살펴보자.
클라이언트 A
- 클라이언트 A 가 ClientBean의 logic 을 호출한다.
- logic() 에서 ProtoType의 addCount()를 호출해 프로토타입 빈의 count를 1 증가시킨다.
클라이언트 B
- 클라이언트 B 는 스프링컨테이너에 요청해 ClientBean을 받는다.
- 여기서 중요한 점은 clientBean이 내부에 가지고 있는 프로토 타입 빈은 과거 즉 A가 호출한 시점에 주입이 끝난 빈이다. 주입시점에 프로토타입 빈이 새로생성이 되고 그 이후에는 이미 생성된걸 사용한다.
- logic() 에서 ProtoType의 addCount()를 호출해 프로토타입 빈의 count를 1 증가시킨다.
문제가 발생했다.
원래는 프로토타입빈을 사용하여 호출될때마다 새로운 빈을 사용하려는 목적이었는데
앞서 주입과정에서 이미 주입된 프로토타입빈을 계속사용하기 때문이다.
* 프로토타입은 계속 생성되지만 이미주입받은걸 계속사용한다.
코드구현
package hello.core.scope;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import static org.assertj.core.api.Assertions.*;
public class SingletonWithPrototype1 {
@Test
void prototypeFind() {
// bean 등록 후 가져오기
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
prototypeBean1.addCount();
assertThat(prototypeBean1.getCount()).isEqualTo(1);
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
prototypeBean2.addCount();
assertThat(prototypeBean2.getCount()).isEqualTo(1);
}
@Test
void singletonClientUsePrototype() {
// 빈등록
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
// clien bean 가져옴
ClientBean clientBean1 = ac.getBean(ClientBean.class);
int count1 = clientBean1.logic();
assertThat(count1).isEqualTo(1);
// 1이 되어야 하는데 이미 주입받은 Prototyped을 써서 2가 되었다.
ClientBean clientBean2 = ac.getBean(ClientBean.class);
int count2 = clientBean2.logic();
assertThat(count2).isEqualTo(2);
}
// ClientBean 은 싱글톤
@Scope("singleton")
static class ClientBean {
// 생성시점에 주입 그래서 계속 같은걸 쓴다.
// 처음 주입때만 생성한다.
private final PrototypeBean prototypeBean;
// 이때 PrototypeBean을 요청하고 찾는다.
// 이미 생성시점에 만들어 진 애를 사용한다.
@Autowired
public ClientBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
// prototype을 써서 값 증가후 값 리턴
public int logic() {
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
@Scope("prototype")
static class PrototypeBean {
private int count = 0;
public void addCount() {
count++;
}
public int getCount() {
return count;
}
@PostConstruct
public void inti() {
System.out.println("PrototypeBean.init" + this);
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy" + this);
}
}
}
해결방법
아래처럼 호줄 해줄때 Bean을 가져온다.
하지만 코드도 지저분 하고 스프링에 너무 의존적인 코드가 된다.
package hello.core.scope;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import static org.assertj.core.api.Assertions.assertThat;
public class SingletonWithPrototype1 {
@Test
void prototypeFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
prototypeBean1.addCount();
assertThat(prototypeBean1.getCount()).isEqualTo(1);
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
prototypeBean2.addCount();
assertThat(prototypeBean2.getCount()).isEqualTo(1);
}
@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);
}
// ClientBean 은 싱글톤
@Scope("singleton")
static class ClientBean {
// 생성시점에 주입 그래서 계속 같은걸 쓴다.
// 처음 주입때만 생성한다.
// private final PrototypeBean prototypeBean;
@Autowired
ApplicationContext ac;
// 이때 PrototypeBean을 요청하고 찾는다.
// 이미 생성시점에 만들어 진 애를 사용한다.
// @Autowired
// public ClientBean(PrototypeBean prototypeBean) {
// this.prototypeBean = prototypeBean;
//}
public int logic() {
// 여기가 핵심이다. 그냥 부를때 마다 빈을 가져온다.
// 그렇게 되면 프로토타입의 동작을 제대로 한다.
PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
prototypeBean.addCount();
return prototypeBean.getCount();
}
}
@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");
}
}
}
핵심코드
public int logic() {
// 여기가 핵심이다. 그냥 부를때 마다 빈을 가져온다.
// 그렇게 되면 프로토타입의 동작을 제대로 한다.
PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
prototypeBean.addCount();
return prototypeBean.getCount();
}
핵심코드만 나눠서 보자
- DI 가 아니라 Dependency 를 찾아서 가져온다.
- 이러한 방법을 DL(Dependency lookup) 이라고 한다.
- 하지만 코드가 스프링에 종속적이고 테스트도 힘들다.
- 그렇다면 DL을 해주는 간편한 방법이 없을까? 스프링에 있다 Part 3에서 알아보자
728x90
반응형
'Spring > spring 기초 스터디' 카테고리의 다른 글
[Spring] Bean Scope Web: part4 (0) | 2021.06.15 |
---|---|
[Spring] Bean Scope Prototype: part3 (0) | 2021.06.14 |
[Spring] Bean Scope Prototype: part1 (0) | 2021.06.01 |
[Spring] Bean Scope (0) | 2021.06.01 |
[Spring] Bean Callback (0) | 2021.05.29 |
댓글