티스토리 뷰

SPRING

[SPRING] 싱글톤 컨테이너

혀내 2022. 7. 16. 19:49
반응형
매번 서로 다른 고객이 요청을 보낼 때마다 memberService 객체를 생성해야할까?

매우 비효율적! 1000명이 요청을 보내면 1000개의 객체가 만들어진다.

 

그렇다면 객체를 딱 하나만 생성하고 이 객체를 모두 공유해서 사용하도록 하자. == "싱글톤"

 

 

 

싱글톤 패턴

  • 외부에서 호출할 수 있는 생성자를 private으로 막는다.
  • 누군가 객체를 조회할 때마다 클래스에 미리 생성해 두었던 로컬 변수를 항상 반환한다.
package hello.core.singleton;

public class SingletonService {
    //1. static 영역에 객체를 딱 1개만 생성해둔다.
    private static final SingletonService instance = new SingletonService();
    
    //2. public으로 열어서 객체 인스터스가 필요하면 이 static 메서드를 통해서만 조회하도록 허용한다.
    public static SingletonService getInstance() {
        return instance;
    }
    
    //3. 생성자를 private으로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다.
    private SingletonService() {
    }
    
    public void logic() {
    	System.out.println("싱글톤 객체 로직 호출");
    }
}

 

싱글톤 패턴의 문제점

  • 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존한다.
  • DIP를 위반한다.
  • 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
  • 테스트하기 어렵다.
  • 내부 속성을 변경하거나 초기화 하기 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.
  • 유연성이 떨어진다.... 등등등

그러나, 스프링 빈은 이전에도 말했듯 기본적으로 싱글톤으로 관리된다. (위의 문제점을 모두 해결한 방식으로)

 

 

싱글톤 방식의 주의점

  • 무상태(stateless)로 설계해야 한다. → 한 객체를 여러 사용자가 공유해 사용하기 때문이다.
    • 특정 클라이언트에 의존하는 필드가 있으면 안된다.
    • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
    • 가급적 읽기만 가능하다.

아래는 무상태로 설계되지 않은 잘못된 예시이다.

package hello.core.singleton;

public class StatefulService {
 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;
 }
}

 

 

 

첫번째 궁금증

그렇다면 스프링은 어떻게 빈 객체를 싱글톤으로 관리하는걸까?

@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 스프링 빈을 불러와 클래스 이름을 확인해보자.

 

분명 우리가 만든 클래스는 hello.core.AppConfig 인데 실제 빈 객체 클래스명에는 뒤에 복잡한 문자열이 추가되어 있다.

 

이렇듯 스프링은 내가 만든 클래스가 아니라 CGLIB라는 바이트코드 조작 라이브러리를 사용해 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한다!

 

 

두번째 궁금증

만약 AppConfig의 @Configuration 어노테이션을 삭제한다면 어떻게 될까?

 

//@Configuration 삭제
public class AppConfig {
    @Bean
    public MemberService memberService() {
        System.out.println("call AppConfig.memberService");
        return new MemberServiceImpl(memberRepository());
    }
    
    @Bean
    public OrderService orderService() {
        System.out.println("call AppConfig.orderService");
        return new OrderServiceImpl(
        memberRepository(),
        discountPolicy());
    }
    
    @Bean
    public MemberRepository memberRepository() {
        System.out.println("call AppConfig.memberRepository");
        return new MemoryMemberRepository();
    }
    
    @Bean
    public DiscountPolicy discountPolicy() {
    	return new RateDiscountPolicy();
    }
}

 

bean = class hello.core.AppConfig

AppConfig는 Configuration, 즉 설정 객체가 아닌 순수한 빈 객체로 등록된다.

 

call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.orderService
call AppConfig.memberRepository
call AppConfig.memberRepository

그리고 빈 설정 정보를 등록할 때 memberRepository가 3번 호출된다. 즉, 싱글톤이 깨진다.

 

 

출처:

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

 

반응형