
오늘은 스프링의 싱글톤 이라는 개념과 이를 컨테이너 내에서 어떻게 사용되는지에 대해서 공부하였다.

public class SingletonTest {
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
void pureContainer() {
AppConfig appConfig = new AppConfig();
MemberService memberService1 = appConfig.memberService();
MemberService memberService2 = appConfig.memberService();
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
Assertions.assertThat(memberService1).isNotSameAs(memberService2);
}
}
public class SingletonService {
private static final SingletonService instance = new SingletonService();
// 스태틱 영역에 딱 하나 올라감
public static SingletonService getInstance(){
return instance; //조회하는 역할
}
private SingletonService(){
}
public void logic(){
System.out.println("싱글톤 객체 로직 호출");
}
}
싱글톤 패턴을 적용하면 고객의 요청마다 인스턴스를 생성하지 않을 수 있으므로 메모리를 낭비하지 않아도 된다.
싱글톤 패턴 문제점
"하지만 spring은 싱글톤의 모든 문제점을 없애주고, 장점은 다 가져와준다."
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다.
싱글톤 컨테이너

##스프링의 기본 빈 등록 방식은 싱글톤이지만, 싱글톤 방식만 지원하는 것은 아니다. 요청할 때 마다 새로운 객체를 생성해서 반환하는 기능도 제공한다.이를 빈 스코프라고 한다. -> 99퍼센트 싱글톤 방식이다.
싱글톤 패턴이든, 싱글톤 컨테이너 던, 객체 인스턴스를 하나만 생성하고 공유하는 방식은 조심해야한다. 인스턴스가 stateful이기 때문에
무상태로 설계해야한다(stateless).
왜 stateless로 해야하나?
같은 인스턴스를 사용하기 때문에 다른 사용자가 설정해둔 인스턴스 값에 다른 사용자가 정보를 바꿀 수도 있기 때문이다.
공유필드는 항상 조심해야한다. -> 항상 spring은 stateless로 설계하자.
실무로 몇년에 한번씩 꼭 만나는 문제이므로 조심하자.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository()); // 멤버 서비스를 부르게 된다면, 생성자 주입.
}
@Bean
public MemberRepository memberRepository() { // 메모리 멤버 리포지토리를 사용할거야
return new MemoryMemberReopository();
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy()); // 여기서 구현체를 지정해줄 수있음
}
@Bean
public DiscountPolicy discountPolicy(){ //할인 정책은 고정으로 할거야
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
public class ConfigurationSingletonTest {
@Test
void configurationTest(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
MemberRepository memberRepository1 = memberService.getMemberRepository();
MemberRepository memberRepository2 = orderService.getMemberRepository();
System.out.println("memberService -> memberRepository1 = " + memberRepository1);
System.out.println("orderService -> memberRepository2 = " + memberRepository2);
System.out.println("orderService -> memberRepository2 = " + memberRepository);
Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
}
}
스프링 컨테이너는 싱글톤 레지스트리다. 따라서 스프링이 싱클톤이 되는 것을 보장해야하는데, 자바코드까지 직접 관여하기는 어렵다.
-> 그래서 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용한다.
@Test
void configurationDeep() {
ApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class);
//AppConfig도 스프링 빈으로 등록된다.
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
//출력: bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$bd479d70
}


#참고 AppConfig@CGLIB는 AppConfig의 자식 타입이므로, AppConfig 타입으로 조회 할 수 있다
-> 싱글톤이 적용되지 않는다. 이유는 중복된 인스턴스 생성 요청에도 계속해서 인스턴스를 생성하기 때문이다.
싱글톤이라는 개념이 처음에는 생소했는데, 공부할수록 java에서 static영역이 생각나서 이번에는 쉽게 이해가 되었다. 즉 싱글톤 컨테이너란, 컨테이너 내의 빈을 고객이 요청할때 빈에 대한 인스턴스를 새로운 메모리에 생성하고 고객에게 할당하는 것이 아니라, 하나의 메모리공간에 생성하고 고객에게 할당해주는것. 점점 공부를 하지 않는 내가 보이는데 계속 할 수 있도록.