[Spring] 싱글톤과 스프링 컨테이너

2022. 10. 20. 21:40

싱글톤 (Singleton) 패턴

싱글톤 패턴이란 애플리케이션 내에서 사용하는 객체의 인스턴스를 하나만 공유해서 사용하는 패턴이다.

public class AppConfig {
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(),discountPolicy());
    }

    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}
public class SingletonTest {
    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberService memberService1 = appConfig.memberService();
        MemberService memberService2 = appConfig.memberService();

        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);
    }
}

호출할 때마다 new 키워드로 새로운 인스턴스를 반환하기 때문에 동시에 많은 클라이언트가 요청을 하게 되면 사용되는 메모리가 늘어나 서버 입장에선 부담스러워진다. 이를 해결할 수 있는 방법이 싱글톤 패턴이다.

public class SingletonService {
    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance() {
        return instance;
    }

    private SingletonService() {}
}

싱글톤 패턴은 위와 같이 하나의 인스턴스만 static 영역에 만들어놓고 오는 요청마다 같은 인스턴스를 반환해주기 때문에 모든 요청에 같은 인스턴스를 공유할 수 있게 보장해준다. 하지만 싱글톤 패턴은 몇 가지 문제점이 있다.

 

싱글톤 패턴의 문제점

1. 의존관계상 클라이언트가 구체 클래스에 의존하게 된다.

2. 테스트하기가 어렵다.

3. private 생성자로 자식 클래스를 만들기 어렵기 때문에 유연성이 떨어진다.

 

위와 같은 문제들 때문에 싱글톤 패턴은 안티 패턴으로 불리기도 한다. 그러나 스프링 컨테이너는 싱글톤 패턴의 문제들을 해결하면서, 객체 인스턴스를 싱글톤으로 관리해준다.

@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(),discountPolicy());
    }
    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}
public class SingletonTest {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService1 = ac.getBean("memberService",MemberService.class);
        MemberService memberService2 = ac.getBean("memberService",MemberService.class);

        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);
    }
}

같은 코드이지만 스프링의 DI 컨테이너를 적용한 뒤 결과를 보면 같은 인스턴스를 사용하는 것을 확인할 수 있다.

 

AppConfig 클래스엔 @Configuration과 @Bean만 추가했을 뿐 똑같이 new 키워드로 인스턴스를 만들어서 반환해주는 것처럼 보이지만 DI 컨테이너에서는 같은 인스턴스를 반환해준다. 그 이유는 @Configuration 때문이다.

public class SingletonTest {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

        AppConfig appConfig = ac.getBean("appConfig", AppConfig.class);
        System.out.println(appConfig.getClass()); 
        //class hello.core.AppConfig$$EnhancerBySpringCGLIB$$3a6010e2
    }
}

@Configuration이 붙은 AppConfig의 빈을 출력해보면 GCLIB$$xxxx로 순수한 객체가 아닌 다른 인스턴스가 출력된다.

이는 스프링이 CGLIB라는 라이브러리를 사용해서 AppConfig 클래스를 상속받은 다른 객체를 스프링 빈으로 등록한 것이고 이 빈이 싱글톤을 보장해준다.

 

주의할 점은 @Configuration을 빼고 @Bean 애너테이션만 사용하면 같은 인스턴스를 보장하지 않기 때문에 스프링의 설정 정보 클래스에는 항상 @Configuration을 붙여줘야 한다.

 

참고 자료  : 김영한님 스프링 핵심 원리 - 기본편

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

BELATED ARTICLES

more