스프링 컨테이너는 싱글톤 문제점을 해결하면서 객체를 싱글톤으로 관리한다!!!(획기적인데??)
- 그게 spring bean이다?!?
진짜인지 test해보자
AppConfig
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
Test Code
@Test
@DisplayName("spring container 싱글톤 테스트")
void springContainerSingletonTest() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//1. 조회: 호출 할 때마다 객체를 생성하는지
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
Assertions.assertThat(memberService1).isEqualTo(memberService2);
}
ac를 호출해주고, getbean을 해주면 같은 컨테이너를 불러오는 것을 확인할 수 있다.
(new MemberService를 해줄 필요가 없다.)
거의 대부분은 spring bean을 사용한다.
하지만 중요한 것이 있으니....!
여러 클라이언트가 공용해서 사용하기 때문에 stateless로 설계해야한다!
특정 클라이언트에 의존적이거나, 특정 클라이언트가 값을 변경할 수 있게 두면 안된다.
가급적 읽기만 가능해야한다.
그럼 test코드를 통해서 StatefullService의 문제점을 알아보자
public class StatefullService {
private int price;//상태를 유지하는 필드
public void order(String name, int price) {
System.out.println("name = " + name + " price = "+price);
this.price = price; //여기가 문제!
}
public int getPrice() {
return price;
}
}
package hello11.core.singleton;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import static org.junit.jupiter.api.Assertions.*;
class StatefullServiceTest {
@Test
@DisplayName("statefulService singleton에서 주의점")
void statefulServiceSingleton() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(StatefullService.class);
StatefullService statefulService1 = ac.getBean("statefullService", StatefullService.class);
StatefullService statefulService2 = ac.getBean("statefullService", StatefullService.class);
statefulService1.order("userA", 1000);
statefulService2.order("userB", 2000);
int price = statefulService1.getPrice();
System.out.println("price = " + price);
}
static class TestConfig {
@Bean
public StatefullService statefullService() {
return new StatefullService();
}
}
}
int price = statefulService1.getPrice();
System.out.println("price = " + price);
여기서 price는 얼마가 나올까??
정답은 2000원이다. 분명히 statefulService1의 userA가 주문한 가격을 조회할 목적이었지만, 공용필드값을 사용함으로써 2000원이 나온것! 이것을 정말 조심해야한다. (서비스 망한다)
즉 공용 필드 값을 설정하면 안된다.
그럼 수정해보자
package hello11.core.singleton;
public class StatefullService {
// private int price;//상태를 유지하는 필드
public int order(String name, int price) {
System.out.println("name = " + name + " price = "+price);
// this.price = price; //여기가 문제!
return price;
}
// public int getPrice() {
// return price;
// }
}
@Test
@DisplayName("statefulService singleton에서 주의점")
void statefulServiceSingleton() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(StatefullService.class);
StatefullService statefulService1 = ac.getBean("statefullService", StatefullService.class);
StatefullService statefulService2 = ac.getBean("statefullService", StatefullService.class);
int userA = statefulService1.order("userA", 1000);
int userB = statefulService2.order("userB", 2000);
// int price = statefulService1.getPrice();
System.out.println("price = " + userA);
System.out.println("price = " + userB);
지역변수로 return 하게 하자,
각각 1000, 2000씩 잘 나오는 것을 확인할 수 있다.