Spring ‐ 싱글톤 컨테이너 - dnwls16071/Backend_Study_TIL GitHub Wiki

📚 싱글톤 패턴

  • 클래스의 인스턴스가 오직 1개만 생성되는 것을 보장하는 디자인 패턴이다.
  • 객체 인스턴스를 2개 이상 생성하지 못하도록 막아야 한다. 그래서 생성자를 private 접근 제어자로 지정해 외부에서 임의로 객체를 생성할 수 없도록 막아야 한다.
public class SingletonService {

        // 메서드 영역에 하나의 인스턴스가 만들어진다.(힙 영역 X)
	private static final SingletonService INSTANCE = new SingletonService();

        // 메서드 영역에 떠 있는 인스턴스 객체를 조회한다.
	public static SingletonService getInstance() {
		return INSTANCE;
	}

        // 외부에서 new 키워드로 생성자를 호출할 수 없도록 private 접근 제어자로 막는다.
	private SingletonService() {

	}

	public void logic() {
		System.out.println("싱글톤 객체의 로직 호출");
	}
}

테스트 코드 실행 결과

스크린샷 2025-01-20 오전 2 16 37

  • 싱글톤 패턴의 단점
    • 위와 같이 접근 제어자로 생성자를 제한하는 등 싱글톤 패턴을 구현하는 코드 자체가 많다.
    • 의존관계상 클라이언트가 구체 클래스에 의존한다. 결국 적절한 추상화를 사용하지 못하므로 DIP를 위반하게 된다.
    • 테스트하기가 어렵다.
    • 내부 속성을 변경하거나 초기화하기 어렵다.
    • 유연성이 떨어진다.

📚 싱글톤 컨테이너

  • 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다.
  • 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다. 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라고 한다.
  • 고객의 요청이 올 때마다 객체를 생성하는 것이 아니라 아래와 같이 이미 만들어진 객체를 공유해서 객체를 재사용할 수 있다.

스크린샷 2025-01-20 오후 1 59 54

📚 싱글톤 방식의 주의점

  • 싱글톤 패턴은 객체 인스턴스를 하나만 공유해서 사용하기 때문에 상태를 유지하는 설계를 취해서는 안 된다.
  • 무상태로 설계해야 한다. → 특정 클라이언트에 의존적인 필드가 있으면 안되고 특정 클라이언트가 값을 변경할 수 있는 필드가 있어선 안 된다.
  • 필드 대신에 자바에서 공유되지 않는 스택 영역의 지역 변수나 파라미터, 쓰레드 로컬 등을 사용해야 한다.
public class StatefulService {

	private int price;	// 필드 공유

	public void order(String name, int price) {
        System.out.println("상태가 있는(stateful) 서비스: " + name + " - " + price);
		this.price = price;
	}

	public int getPrice() {
		return price;
	}
}
public class StatefulServiceTest {

	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonConfig.class);

	@Test
	@DisplayName("싱글톤은 상태를 보존하는 설계를 하면 장애가 발생한다.")
	void singletonStatefulService() {

		StatefulService statefulService1 = ac.getBean("statefulService", StatefulService.class);
		StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);

		statefulService1.order("userA", 1000);
		statefulService2.order("userB", 2000);

		int price = statefulService1.getPrice();

		assertThat(price).isEqualTo(1000);
	}

	@Configuration
	static class SingletonConfig {

		@Bean
		public StatefulService statefulService() {
			return new StatefulService();
		}
	}
}

테스트 코드 실행 결과

상태가 있는(stateful) 서비스: userA - 1000
상태가 있는(stateful) 서비스: userB - 2000

Expected :1000
Actual   :2000
  • userA가 주문한 금액을 조회해서 나오길 기대했는데 userB가 주문한 금액이 조회해서 나오는 것을 볼 수 있다.
  • 아래와 같이 금액 필드를 공유하지 않도록 price를 파라미터로 사용하면 공유되는 문제를 해결할 수 있다.
public class StatefulService {

        // price를 파라미터로 사용하기
	public int order(String name, int price) {
            System.out.println("상태가 있는(stateful) 서비스: " + name + " - " + price);
	    return price;
	}
}

📚 @Configuration과 싱글톤

  • 구성 파일에 @Configuration 어노테이션을 붙이면 포함된 메서드에 붙어 있는 @Bean을 호출해서 스프링 빈을 생성한다.
  • CGLIB라는 바이트 코드 조작 라이브러리를 통해 구성 파일 클래스를 상속받은 임의의 다른 클래스를 만들고 그 클래스를 스프링 빈으로 등록하여 싱글톤으로 등록하는 것이다.

스크린샷 2025-01-20 오후 2 21 03

  • 만약에 @Bean만 사용하게 되면 싱글톤으로 관리되지 않는다.
  • 설정 정보는 항상 @Configuration & @Bean을 같이 사용해 싱글톤으로 관리하자.