Spring

[SPRING] Singleton

chanyoun 2023. 2. 26. 20:35

[SPRING] Singleton

✔Singleton pattern을 사용하는 이유

대부분 Spring 애플리케이션은 웹 애플리케이션이다.

웹 애플리케이션은 아래와같이 여러 클라이언트의 요청이 동시에 들어오는 경우가 많다.

1

이때 여러 클라이언트로부터 동시에 요청이들어오면 각 클라이언트마다 필요한 객체를 생성해야하고, 따라서 메모리낭비가 발생하게된다.

이러한 현상 즉 각 클라이언트마다 새로운 객체가 생성됨에 따른 메모리낭비를 해결해줄수 있는것이 Singleton 패턴이다.

✔Singleton pattern이란

Singleton pattern은 앞서말했듯 각 클라이언트마다 객체를생성하는게 아닌 클래스 인스턴스를 하나만 생성하여 모든 클라이언트에 대한 작업을 처리한다.

❗ Singleton을 생성하는 많은 방법이있지만 그중 객체를 미리 생성한뒤 static영역에 올려두는 방법으로 진행한다.

Singleton 패턴을 사용해보자.

private생성자를 사용하여 외부에서 임의로 new키워드를 사용하지 못하게 막는다.

private static final SingletonService instance = new SingletonService();

//객체 인스턴스를 조회할수있게 하는 메서드
public static SingletonService getInstance() {
    return instance;
}

또한 static영역에 미리 생성한 하나의 객체를 올려두는 방식을 사용한다.

이렇게 생성된 객체를 통해 클라이언트로부터 요청이 올때마다 새로운 객체를 생성하는것이아닌 기존에 생성해둔 객체를 사용하는것이다.

하지만 이렇게 생성된 클라이언트에는 다양한 문제점이 존재한다. 클라이언트가 구체 클래스에 의존하기 때문에 DIP를 위반하며, 따라서 OCP를 위반할 가능성이 높고, 생성자가 private이기 때문에 자식클래스를 만들기 힘들다. 이러한 문제점을 해결하기위해 나온것이 Singleton container이다.

✔Singeton Container

Singleton Container을 사용하면 위에서말한 Singleton의 단점을 상쇄시킬수있다.

Spring Container는 Singleton Container의 역활을하며 따라서 하나의 bean을 등록할때 Singleton방식을 사용한다.

ApplicationContext ac = newAnnotationConfigApplicationContext(AppConfig.class);

MemberService memberService1 = ac.getBean("memberService",MemberService.class);

MemberService memberService2 = ac.getBean("memberService",MemberService.class);

결론적으로 위 코드에서 memberService1과 memberService2가 같은 객체를 공유하는것을 확인할수 있다.

주의사항

Spring Container를 사용하면 위와같이 memberService1과 memberService2가 같은 객체를 공유한다.

이때 MemberService에 private int price; 와같은 상태를 유지하는 필드가 존재한다 가정하며, 이때 memberService1과 memberService2에대해서 각각 다른값을 지정해준다 생각해보자.

하지만 memberService1과 memberService2는 같은객체를 공유하기때문에 각각 다른값을 지정해줄수 없다. 따라서 Singleton pattern을 사용할때는 상태를 유지하지않는 무상태로 설계를 해야한다.

✔CGLIB

Spring container는 싱글톤 레지스트리다. -> Spring bean이 싱글톤이 되도록 보장을 해준다.

이때 bean이 싱글톤이 되도록 보장해주도록 하는것이 @Configuration 이다.

@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(
            memberRepository(),
            discountPolicy());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    ...
}

AppConfig클래스에있는 메서드를 통해 bean으로 등록하려할때, @Configuration 을 사용해준다면, Spring은 CGLIB(바이트코드 조작 라이브러리)를 통해 각 bean의 싱글톤을 보장한다.

CGLIB를 사용해서 싱글톤을 보장하는 이유

AppConfig코드를 보면 memberService(),orderService() 각메서드가 memberRepository()를 호출하는것을 볼수있다. 즉 Singleton이 깨지는것처럼 (new MemoryMemberRepository(); 이 2번호출되니까) 보이지만 Spring은 이러한 문제를 CGLIB를 통해 해결한다.

2

CGLIB를 통해 위 그림과같이 AppConfig@CGLIB라는 클래스를 만들고 AppConfig가 아닌 AppConfig@CGLIB클래스를 사용해 각각의 bean들이 Singleton을 보장할수 있도록한다.

결과적으로 Spring은 각 빈의 싱글톤을 보장하기위해 CGLIB를 사용하며, CGLIB를 사용하기 위해서는 @Configuration을 사용해야 한다.

🙏Reference