Skip to content

스프링 핵심편 Section5

SH-Seol edited this page May 11, 2024 · 3 revisions

싱글톤 컨테이너

웹 애플리케이션, 싱글톤

  • spring은 기업용으로 탄생
  • 대부분 spring app → web app
    • demon, batch도 개발이 가능
  • web app은 보통 여러 고객이 동시 요청

AppConfig는 요청을 할 때마다 객체를 새로 생성

트래픽이 높아지면 높아질수록 더 많은 객체가 생성,소멸됨 → 메모리 낭비 심함

싱글톤 → 이 문제를 해결

싱글톤 패턴

private static final SingletonService instance = new SingletonService();

public static SingletonService getInstance(){
    return instance;
}

private SingletonService(){

}
public void logic() {
    System.out.println("싱글톤 객체 로직 호출");
}
  • 딱 1개의 객체 인스턴스만 존재해야 하므로, 생성자를 private으로 막아서 혹시라도 외부에서 new 키워드로 객체 인스턴스가 생성되는 것을 막는다.
    • 컴파일 에러로만 많은 에러를 잡을 수 있는 것이 잘 설계한 객체

assertion에서 same, equal

same ⇒ java = ⇒ 진짜 인스턴스가 같은지

equal ⇒ java equals

AppConfig를 모두 싱글톤으로 바꿔야 하나요?

→ spring container에서 알아서 싱글톤으로 해줍니다.

싱글톤 패턴의 문제점

  • 구현 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존 → DIP 위반
    • getInstance를 불러와야 하기 때문
  • client가 구체 클래스에 의존해서 OCP 위반 가능성이 크다.
  • 유연하게 테스트 어렵다.
    • singleton은 미리 instance를 박아 놓기에 설정이 끝나기 때문
  • 내부 속성 변경, 초기화 어려움
  • private 생성자로 자식 클래스 만들기 어려움
  • 안티패턴으로 불리기도 한다.

싱글톤 컨테이너

Spring Container는 싱글톤 패턴의 문제점을 해결, 싱글톤으로 관리.

Spring Bean → Singleton으로 관리되는 Bean

싱글톤 컨테이너

  • Spring Container는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리
    • 컨테이너는 객체를 하나만 생성해서 관리
  • Spring Container→ Singleton Container, Singleton 객체를 생성 관리하는 기능 → Singleton Registry
  • 이러한 기능 덕에 Singleton 패턴의 모든 단점을 해결, 객체를 Singleton으로 유지
    • 지저분한 코드 불필요
    • DIP, OCP, 테스트, private 생성자로부터 자유로움

싱글톤 컨테이너 적용 후

  • client의 요청이 들어올 때 마다 객체를 생성하는 것이 아닌, 이미 만들어진 객체를 공유해서 재사용

스프링 기본 빈 등록 방식은 singleton, but 새로운 객체를 생성해서 반환하는 기능도 제공 → 빈 스코프

싱글톤 방식의 주의점

singleton방식은 여러 client가 하나의 같은 객체 인스턴스를 공유하기 때문에, 싱글톤 객체의 상태는 stateless하게 설계해야 한다.

  • 특정 client에 의존적인 필드가 있으면 안된다.
  • 특정 client가 값을 변경할 수 있는 필드가 있으면 안된다.
  • 가급적 읽기만 가능해야 한다.
  • 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.

image

Spring bean의 필드에 공유 값을 설정하면 큰 장애가 발생

void statefulServiceSingleton(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);

        //ThreadA: A사용자가 10000원 주문
        statefulService1.order("userA", 10000);
        //ThreadA: B사용자가 20000원 주문
        statefulService2.order("userB", 20000);

        //ThreadA: 사용자 A 주문 금액 조회
        int price = statefulService1.getPrice();
        System.out.println("price = " + price);

        Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
    }

statefulService는 같은 객체이기에 10000원에서 20000원으로 바뀌게 됨

private int price; // 상태를 유지하는 필드

    public void order(String name, int price){
        System.out.println("name = " + name + ", price = " + price);
        this.price = price; // 여기가 문제
    }

이렇게 설정되어 있는 것을

//private int price; // 상태를 유지하는 필드

    public int order(String name, int price){
        System.out.println("name = " + name + ", price = " + price);
        //this.price = price; // 여기가 문제
        return price;
    }

이렇게 변경하자

실무에서 이러한 일을 꼭 보게 된다.. thread문제

@Configuration과 싱글톤

Configuration은 싱글톤을 위해 존재하는 것이다.

//@Bean memberService -> new MemoryMemberRepository()
//@Bean orderService -> new MemoryMemberRepository()
  • 객체를 분명 2개를 생성하는 것 처럼 보인다. 에러 아닌가?

TroubleShooting

ConfigurationTest를 진행했는데 모두 다른 것이 떠서 왜인가 확인했더니

AppConfig에 @Bean에 static이 붙어있었기 때문!

지우니 테스트 성공