프로그래밍/Spring

[ Spring ] DI 컨테이너와 싱글톤

Yanoo 2022. 2. 4. 15:00
728x90
반응형

<인프런의 김영한님의 강의를 보고 정리한 내용입니다>

 

 

스프링 컨테이너를 사용하지 않으면 싱글톤이 적용되지 않는다. 간단히 테스트를 해보면,

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

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

    @Bean
    public DiscountPolicy discountPolicy() {
//        return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }

}
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
void pureContainer() {
    AppConfig appConfig = new AppConfig();
    //1. 조회: 호출할 떄 마다 객체를 생성
    MemberService memberService1 = appConfig.memberService();

    //2. 조회: 호출할 때 마다 객체를 생성
    MemberService memberService2 = appConfig.memberService();

    //참조값이 다른 것을 확인
    System.out.println("memberService1 = " + memberService1);
    System.out.println("memberService2 = " + memberService2);

    // memberService1 != memberService2
    assertThat(memberService1).isNotSameAs(memberService2);
}

테스트 코드를 작성하고 확인해보면,

다른 인스턴스가 생성된 것을 확인할 수 있다. 이렇게 매번 다른 인스턴스가 생성되면 효율성이 떨어질 것이다. 이런 단점을 해결하기 위해서 싱글톤으로 코드를 작성해야 한다.

public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance() {
        return instance;
    }

    // new 키워드로 외부에서 인스턴스가 생성되는 것을 막음
    private SingletonService() {
    }

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

이런 식으로 적용해서

@Test
@DisplayName("싱글톤 패턴을 적용한 객체 사용")
void singletonServiceTest() {
    SingletonService singletonService1 = SingletonService.getInstance();
    SingletonService singletonService2 = SingletonService.getInstance();

    System.out.println("singletonService1 = " + singletonService1);
    System.out.println("singletonService2 = " + singletonService2);

    assertThat(singletonService1).isSameAs(singletonService2);
}

테스트를 하면

같은 인스턴스임을 확인할 수 있다. 그런데 이런 싱글톤의 단점은 뭘까?

 

🎈싱글톤의 문제점

  • 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존한다. -> DIP 위반
  • 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
  • 테스트하기 어렵다.
  • 내부 속성을 변경하거나 초기화 하기 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.
  • 결론적으로 유연성이 떨어진다.
  • 안티패턴으로 불리기도 한다.

 

이 문제들을 해결하기 위해 사용하는 것이, 스프링 컨테이너이다. 스프링 컨테이너를 사용하면 이 문제점을 해결해 주는데,

🎁 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.

🎁 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다. 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라 한다.

🎁 스프링 컨테이너의 이런 기능으로 싱글톤의 단점을 해결하고 객체를 싱글톤으로 유지할 수 있다.

  • 싱글톤 패턴을 위한 추가적인 코드 작성 필요 X
  • DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있음.

 

이제 싱글톤으로 적용됐는지 확인해보면,

@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {

    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    //1. 조회: 호출할 떄 마다 객체를 생성
    MemberService memberService1 = ac.getBean("memberService", MemberService.class);
    MemberService memberService2 = ac.getBean("memberService", MemberService.class);

    //참조값이 다른 것을 확인
    System.out.println("memberService1 = " + memberService1);
    System.out.println("memberService2 = " + memberService2);

    // memberService1 != memberService2
    assertThat(memberService1).isSameAs(memberService2);
}

잘 적용된 것을 알 수 있다.

 

참고로 기본 빈 등록방식은 싱글톤이지만, 다른 방식도 지원한다.

 


 

728x90
반응형