[Spring] 싱글톤 컨테이너

2023. 6. 11. 18:44
Section 3 들어가기 전 한 Section 1에서 실습했던 자바 프로젝트를 스프링으로 전환하는 연습 중. 
스프링에서는 객체 인스턴스를 싱글톤으로 자체적으로 관리한다.

 

✔️ 코드를 통해 살펴보기

➡️ 본격적으로 알아보기 전에 먼저 싱글톤 패턴이 무엇인지 간단하게 알아보자.

 

❓싱글톤 패턴

→ 클래스의 인스턴스가 딱 한 개만 생성되는 것을 보장하는 디자인 패턴 
    즉, 생성자를 private 접근 제한해서 외부에서 new 연산자로 생성자를 호출할 수 없도록 막는 것이다. 

public class Singleton {
	
    // (1) static 영역에 필드 선언과 초기화 (한 개)
    private static Singleton singleton = new Singleton();
    
    // (2) private 접근 권한을 갖는 생성자 선언해서 외부에서 new 키워드 사용한 객체 생성을 하지 못하게 막는다.
    private Singleton() {
    }
    
    // (3) 객체 인스턴스가 필요하다면 이 메서드를 통해 조회 가능하다.
    public static Singleton getInstance() {
    	return singleton;
    }

 

1. 이전 코드에서의 싱글톤 패턴

public class Main {
    public static void main(String[] args) {
        
        AppConfigurer appConfigurer = new AppConfigurer();
        
        OrderApp orderApp = new OrderApp(
                appConfigurer.productRepository(),
                appConfigurer.menu(),
                appConfigurer.cart(),
                appConfigurer.order()
        );

        orderApp.start();
    }
}
public class AppConfigurer {

    public ProductRepository productRepository() {
        return new ProductRepository();
    }

    public Menu menu() {
        return new Menu(productRepository().getAllProducts());
    }

    public Cart cart() { 
        return new Cart(productRepository(), menu()); 
    }

    public Discount discount() {
        return new Discount(
                new DiscountCondition[]{
                        new CozDiscountCondition(new FixedRateDiscountPolicy(10)),
                        new KidDiscountCondition(new FixedAmountDiscountPolicy(500))
                });
    }

    public Order order() {
        return new Order(cart(), discount()); // 📍
    }
}
  • 위 코드를 보면 📍 표시된 order() 메서드에서 인스턴스를 생성하면서  cart() 를 호출하고 있다. 이렇게 되면 Main에서 cart()를 통해 만든 Cart 인스턴스와 AppConfigurer의 order() 내 cart()를 통해 만들어진 Cart 인스턴스(📍)는 주소값이 다른 별개의 인스턴스가 되는 문제가 생긴다. 
    → 실제로 주문하기를 눌렀을 때 장바구니에 담긴 상품 목록이 안 보이는 문제가 생겼다. 위에서 설명한 것과 같이 장바구니 목록의 인스턴스 주소값과 주문하기 목록의 인스턴스 주소값이 다르기 때문이다. 그러니 당연히 안에 담긴 값들이 다를 수 밖에 없다.
  • 그렇다면 해결 방안은 Cart의 인스턴스가 단 한 번만 만들어지도록 하여 공유할 수 있도록 해야한다. 
public class AppConfigurer {

    private Cart cart = new Cart(productRepository(), menu()); // 📍 추가

    public ProductRepository productRepository() {
        return new ProductRepository();
    }

    public Menu menu() {
        return new Menu(productRepository().getAllProducts());
    }

    // 📍 추가
    public Cart cart() {
        return cart;
    }

    public Discount discount() {
        return new Discount(
                new DiscountCondition[]{
                        new CozDiscountCondition(new FixedRateDiscountPolicy(10)),
                        new KidDiscountCondition(new FixedAmountDiscountPolicy(500))
                }
        );
    }

    public Order order() {
        return new Order(cart(), discount());
  • 인스턴스가 단 한 번만 생성될 수 있도록 AppConfigurer에 cart 필드를 정의하고 초기화를 하고 cart() 메서드가 리턴값으로 cart를 주면 이제는 주소값이 동일한 Cart 인스턴스가 리턴되도록 보장할 수 있게 된다. 
    • 만약 여러 개의 동시다발적인 고객 요청을 처리해야 하는 웹 애플리케이션 요청이 있을 때마다 최초에 단 하나의 객체를 생성해 두고 요청이 돌아올 때마다 같은 객체를 공유한는 방법으로 메모리 낭비를 최소화할 수 있다. 
  • 이러한 장점도 있지만 단점도 있다. 그렇기 때문에 꼭 필요한 경우가 아니면 사용을 지양하는 것이 좋다. 
    1. 싱글톤 패턴의 구현코드 자체가 많이 들어간다
    2. 클라이언트가 구체 클래스에 의존한다. → DIP 위반
    3. 클라이언트가 구체 클래스에 의존하여 OCP를 위반할 가능성이 높다.
    4. 코드 유연성이 많이 떨어진다.

 

 

2. 싱글톤 컨테이너

➡️ 스프링 컨테이너는 싱글톤 패턴 코드를 직접 작성하지 않아도 내부적으로 객체 인스턴스를 싱글톤으로 관리함으로써 싱글톤 패턴이 가지는 단점을 효과적으로 극복할 수 있다. 

➡️ 이전 게시글에서 스프링 전환 후의 AppConfigurer 클래스를 볼 수 있는데 여기는 싱글톤 패턴에 대한 코드가 생략되고 메서드 호출 시 빈 객체를 생성하는 다른 메서드들과 같은 모양을 하고 있는 것을 알 수 있다. 

➡️ 출력 결과를 보면 스프링 컨테이너가 내부적으로 객체들을 싱글톤으로 관리한다는 사실을 알 수 있다. 

➡️ 또한 스프링 컨테이너는 싱글톤 컨테이너 역할을 수행한다.
    : 싱글톤 레지스트리(Singleton Registry)
       → 싱글톤으로 객체를 생성 및 관리하는 기능

       → 스프링은 CGLB라는 바이트코드 조작 라이브러리를 사용하여 싱글톤 레지스트리를 가능하게 한다. 

BELATED ARTICLES

more