ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 싱글톤과 만능 스프링 컨테이너
    Server/Spring 2024. 9. 22. 17:12
    반응형

    안녕하세요, 여러분! 오늘은 스프링에서 싱글톤을 어떻게 관리하는지에 대해 알아보는 시간을 가져보려고 합니다.

     

    먼저, 싱글톤 패턴에 대해 간단히 짚고 넘어갈게요. 싱글톤은 인스턴스를 오직 한 개만 생성하여 사용하는 디자인 패턴이에요. 그런데 이 싱글톤을 구현하려면 조금 귀찮은 작업들이 필요하죠.

     

    출처: Tecoble

    대표적인 싱글톤 패턴을 살펴보아요.

    public class Singleton {
        private static final Singleton instance = new Singleton();
        
        private Singleton() {} // private 생성자로 외부에서의 생성을 막음
        
        public static Singleton getInstance() {
            return instance; // 미리 생성된 인스턴스 반환
        }
    }

     

    위의 코드처럼 private 생성자로 외부에서의 인스턴스 생성을 막고, getInstance() 메서드를 통해 미리 생성된 인스턴스를 반환하도록 구현해야 해요.

    하지만 이런 방식에는 몇 가지 문제점이 있어요.

    1. 싱글톤 패턴 구현 부분이 클라이언트 코드에 노출됨
    2. 싱글톤 인스턴스가 너무 많은 책임을 갖게 됨 (SRP 위반)
    3. 테스트하기 어려움
    4. 내부 속성을 변경하거나 초기화하기 어려움
    5. private 생성자로 자식 클래스 만들기 어려움

    하지만! 우리는 스프링을 사용하잖아요. 만능 스프링 컨테이너를 사용하면 이런 문제점들을 모두 해결할 수 있어요!

     

    스프링 컨테이너와 싱글톤

    출처: 브리도의 개발일지

     

    스프링 컨테이너는 싱글톤 객체를 생성하고 관리하는 싱글톤 레지스트리 기능을 제공해요. 컨테이너에 의해 관리되는 빈들은 기본적으로 싱글톤으로 생성되어 관리 되는거죠.

     

    @Configuration
    public class AppConfig {
    
        @Bean
        public MemberService memberService() {
            return new MemberServiceImpl(memberRepository());
        }
    
        @Bean
        public MemberRepository memberRepository() {
            return new MemoryMemberRepository();
        }
    }

     

    @Configuration@Bean을 사용하여 스프링 빈을 등록하면, 스프링 컨테이너가 알아서 싱글톤으로 관리해줘요.

     

    여기서 한 가지 궁금증이 생길 수 있죠. "아니, memberService()와 memberRepository()는 분명 여러번 호출될텐데... 싱글톤이 깨지는 거 아냐??"

    하지만 걱정 마세요! 스프링은 이런 의문도 이미 알고 있답니다. 😎

     

    스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig를 상속받은 임의의 다른 클래스를 만들고, 그 클래스를 스프링 빈으로 등록합니다. 이 임의의 클래스가 바로 싱글톤을 보장해주는 비밀병기라고 할 수 있죠!

     

    @Bean
    public MemberRepository memberRepository() {
        if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있다?) {
            return 스프링 컨테이너에서 찾아서 반환;
        } else { // 스프링 컨테이너에 없으면
            return 기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록 후 반환;
        }
    }

     

    따라서 memberService()와 memberRepository()는 각각 한번만 생성되고, 같은 인스턴스가 반환되는 것이 보장돼요.

     

    주의사항

    싱글톤 패턴을 사용할 때는 주의해야 할 점이 있어요. 바로 상태를 유지(stateful)하게 설계하면 안된다는 것!

     

    public class MemberService {
        private int price;
    
        public void order(String name, int price) {
            System.out.println(name + " 님이 " + price + "원을 주문하셨습니다.");
            this.price = price; // 여기가 문제! 
        }
    }

     

    만약 price와 같이 공유되는 필드에 값을 설정하게 되면, 멀티스레드 환경에서 치명적인 문제가 발생할 수 있어요. A사용자가 10000원을 주문하고, B사용자가 20000원을 주문했는데 A사용자의 주문 금액이 20000원으로 바뀌어버리는 참사... 😱

    따라서 스프링 빈은 항상 무상태(stateless)로 설계해야 해요!

     


    마치며

    지금까지 스프링에서 싱글톤을 어떻게 관리하는지 알아보았어요. 핵심은 스프링 컨테이너에게 모든 것을 맡기라는 것!

     

    만약 싱글톤으로 관리하고 싶은 객체가 있다면 @Configuration@Bean을 사용하여 스프링 빈으로 등록만 하세요. 스프링이 알아서 싱글톤 인스턴스를 생성하고 관리해줄 거예요. 우리는 비즈니스 로직에만 집중하시면 됩니다. 😄

     

    이상으로 스프링 싱글톤에 대한 내용을 마칠게요. 궁금한 점이나 잘못된 점 있으면 편하게 댓글 달아주세요!

     

    비가 오는 날엔 ~
    RainyCode를 찾아와
    밤을 새워 기다릴게...
    반응형
Designed by Tistory.