보통 웹 애플리케이션은 여러 고객이 동시에 요청을 한다.
public class SingletonTest{
@Test
void pureContainer(){
AppConfig appConfig = new AppConfig();
// 1 조회
MemberService memberService1 = appConfig.memberService();
//2 조회
MemberService memberService2 = appConfig.memberService();
// memberService1 != memberService2
assertThat(memberService1).isNotSameAs(memberService2);
}
}
스프링 아닌 순수한 DI 컨테이너인 AppConfig는 요청을 할 때마다 새로운 객체를 생성해야한다.
그러다보니, 메모리 낭비가 심하다. 즉, 트래픽이 초당 100이 나오면 초당 100개의 객체가 생성 소멸을 반복한다.
이걸 해결하려면, 해당 객체가 딱 1개만 생성되고, 공유하도록 하면 된다. -> 싱글톤 패턴
* 싱글톤 패턴
클래스의 인스턴스(객체)가 딱 1개만 생성되도록 보장한다.
즈, 객체의 인스턴스가 2개 이상 생성하지 못하도록 막아야 한다.
-> private 생성자를 사용해서 외부에서 new를 호출하지 못하도록 막아야한다.
public class SingletonService{
// static 영역에 객체를 딱 한 개 생성.
private static final SingletonService instance = new SingletonService();
// getter 생성.
public static SingletonService getInstance(){
return instance;
}
// 생성자를 private으로 접근 불가 하기.
private SingletonService(){}
But, 싱글톤의 문제점들이 있다.
- 싱글톤 패턴을 따로 구현해야하므로, 코드 자체가 많이 들어간다.
- 클라이언트가 구현 클래스에 의존하므로, DIP를 위반한다.
- 내부 속성을 변경하거나 초기화하기 어렵다(생성자가 접근불가로 되어있어서)
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤(1개 생성)으로 관리한다.
* 싱글톤 컨테이너
스프링 컨테이너는 싱글턴 패턴을 적용하지 않아도, 객체 인스턴스들을 싱글톤으로 관리한다.
생각해보니, 컨테이너 생성과정을 떠올려보면 컨테이너는 객체를 하나만 생성했다.
이렇다보니, 싱글톤 패턴을 따로 구현하지 않아도 되고, private 생성자로부터 자유롭다.
void springContainer(){
ApplicationContext as = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
assertThat(memberService1).isSameAs(memberService2);
}
* 싱글톤 방식의 주의점
싱글톤 패턴이든, 싱글톤 컨테이너이든 간에 객체 인스턴스를 하나만 생성해서 공유하기 때문에
싱글톤 객체를 상태를 유지하게 해서는 안된다.
즉, 무상태(stateless)로 설계해야한다.
-> 특정 클라이언트에 의존적인 필드가 있으면 안된다.
-> 특정 클라이언트가 값 변경을 하게 해서는 안된다.
-> 읽기만 가능하게 하는 것이 좋다.
-> 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal등을 사용해야 한다.
public class StatefulService{
private int price;
public void order(String name, int price){
this.price = price; // 문제!!!! 값을 변경하잖아
}
}
그런데 뭔가 이상하다. 전의 AppConfig코드를 보자.
@configuration
public class AppConfig{
@Bean
public MemberService memberService(){
return new MemberServieImpl(memberRepository());
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
}
memberService빈와 orderService빈을 만드는 코드 모두 memberRepository()를 호출한다.
이러면 각자 다른 두 개의 MemoryMemberRepository가 생성되면서 싱글톤이 깨져보인다.
어떻게 해결할까?
비밀은 @Configuration을 적용한 AppConfig에 있다.
CGLIB라는 바이트코드 조작 라이브러리를 활용해서 AppConfig클래스를 상속받은 임의의 다른 클래스를 만들고
이걸 스프링 빈으로 등록한다!
만약 @Configuration이 적용되지 않고, @Bean만 적용하면?
싱글톤이 적용되지 않는다.
그러니, 항상 @Configuration을 활용해보자~!
'스프링 강의 필기 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
섹션 7 - (2). 의존관계 자동 주입 (0) | 2022.07.13 |
---|---|
섹션 7 -(1) 의존관계 자동 주입 (0) | 2022.07.13 |
섹션 6. 컴포넌트 스캔 (0) | 2022.07.13 |
섹션4 - 스프링 컨테이너와 스프링 빈 (0) | 2022.07.09 |
섹션 3. 스프링 핵심 원리 이해2 - 객체 지향 원리 적용 (0) | 2022.07.09 |