싱글톤 패턴 문제점
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤(1개만 생성)으로 관리한다.

싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지하게 설계하면 안된다.
무상태로 설계해야 한다.
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애가 발생할 수 있다.
@Test
void statefulServiceSingleton() {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StateFulService statefulService1 = ac.getBean(StateFulService.class);
StateFulService statefulService2 = ac.getBean(StateFulService.class);
// ThreadA: A사용자 10000원 주문
statefulService1.order("userA", 10000);
// ThreadB: B사용자 20000원 주문
statefulService2.order("userB", 20000);
// ThreadA: 사용자A 주문 금액 조회
int price = statefulService1.getPrice();
System.out.println("price = " + price);
Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
System.out.println("AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
System.out.println("AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
System.out.println("AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
memberService 빈을 만드는 코드를 보면 memberRepository()를 호출한다.
- 이 메서드를 호출하면 new MemoryMemberRepository()를 호출한다.
orderService 빈을 만드는 코드도 동일하게 memberRepository()를 호출한다.
- 이 메서드를 호출하면 new MemoryMemberRepository()를 호출한다.
결과적으로 각각 다른 2개의 MemoryMemberRepository가 생성되면서 싱글톤이 깨지는 것 처럼 보인다. 스프링 컨테이너는 이 문제를 어떻게 해결할까?
@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("been = " + bean.getClass());
//출력: bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$bd479d70
순수한 클래스라면 class hello.core.Appconfig가 출력되어야 한다.
그런데 예상과는 다르게 클래스 명에 xxxCGLIB가 붙으면서 상당히 복잡해진 것을 볼 수 있다.
이것은 내가 만든 클래스가 아니라 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것이다.

그 임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다.

참고 AppConfig@CGLIB는 APppConfig의 자식 타입이므로, AppConfig 타입으로 조회 할 수 있다.
@Configuration을 적용하지 않고, @Bean만 적용하면 어떻게 될까?
@Configuration을 붙이면 바이트코드를 조작하는 CGLIB 기술을 사용해서 싱글톤을 보장하지만, 만약 @Bean만 적용하면 어떻게 될까?
@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("been = " + bean.getClass());
@Configuration을 제거하고 위 코드를 실행하면 출력 결과는 bean = class hello.core.Appconfig가 나온다.
이 출력 결과를 통해서 AppConfig가 CGLIB 기술 없이 순수한 AppConfig로 스프링 빈에 등록된 것을 확인할 수 있다.
그러므로 싱글톤이 적용되지 않고 호출할 때 마다 각기 다른 객체를 생성한다.
정리
참고자료: 스프링 핵심 원리 - 기본편(인프런 김영한)