본문 바로가기

WINK-(Web & App)/Spring Boot 스터디

[2024-2 Spring Boot 스터디] 김아리 #5주차

반응형

[싱글톤 등장 배경]

스프링은 보통 웹 애플리케이션 개발에 많이 사용된다. 웹 애플리케이션 특성상 여러 고객이 동시에 요청한다. 이때 고객이 서비스를 요청할 때마다 DI 컨테이너인 AppConfig가 새로운 서비스 객체를 생성한다면 초당 몇 백, 몇 만개의 객체를 생성해야 해야 하므로 메모리 낭비가 심하다. 

--> 해결방안: 해당 객체가 1개만 생성되고 이것을 공유하도록 설계하는 싱글톤 패턴을 적용하면 된다.

 

[싱글톤 패턴]

객체를 딱 1개만 만들어 그것을 재사용한다.

하지만 여러 개의 문제점을 갖고 있다.

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

결론적으로 유연성이 떨어진다고 할 수 있다. 

--> 해결방안 : 싱글톤 컨테이너

 

[싱글톤 컨테이너]

스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도 객체를 싱글톤으로 관리한다. 따라서 굳이 싱글톤 패턴을 위한 코드를 쓸 필요 없고 DIP, OCP, 테스트, private 생성자로부터 자유롭게 싱글톤을 사용할 수 있다.


[싱글톤 주의점]

싱글톤은 항상 무상태(stateless)를 유지해야 한다.
특정 클라이언트에 의존적인 필드가 있거나 변경 가능한 필드가 있어선 안된다. 
상태를 유지할 경우, 예를 들어 사용자 a가 상품을 주문한 뒤 가격을 조회하기 바로 직전,  또 다른 사용자 b가 상품을 주문해 그 상품의 가격으로 필드가 변동되어 큰 문제가 발생한다. 따라서 공유필드는 조심해야 한다. 스프링 빈은 항상 무상태로 설계할 것을 명심하자.

그렇다면 어떻게 무상태로 설계할까?
필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal을 사용한다.


[@Configuration의 비밀]

@Configuration은 싱글톤을 위해 등장했다.

AppCofig에서 memberService와 orderService 빈을 만드는 코드를 보면 모두 memberRepository()를 호출한다. 이 메서드를 호출하면 new MemoryMemberRepository()를 호출한다. 이렇게 보면 새로운 MemoryMemberRepository가 여러 개 만들어지는 것처럼 보인다. 하지만 직접 테스트해보면 모두 같은 MemoryMemberRepository 객체를 참조하고 있다는 것을 알 수 있다. 빈을 만드는 코드에 로그를 남겨 몇 번 call되는지 확인해봐도 한번만 call되는 것을 알 수 있다. 어떻게 가능한걸까? 스프링이 자바 코드를 조작해버린걸까?(그건 아니다.)

스프링 컨테이너는 싱글톤 레지스트리이므로 스프링 빈이 싱글톤이 되도록 보장해주어야 한다. 그래서 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용한다.


AppConfig 스프링 빈을 조회해서 클래스 정보를 살펴보면 클래스 명에 xxxCGLIB이 붙어있는 것을 알 수 있다. 이 CGLIB가 바로 바이트코드 조작 라이브러리이다. 스프링은 이 라이브러리를 사용해 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고 그 다른 클래스를 스프링 빈으로 등록한 것이다. 

따라서 스프링 빈으로 등록되는 클래스는 우리가 만든 AppConfig.class가 아니라 AppConfig를 상속받는 임의의 다른 클래스이다.

AppConfig@CGLIB 예상 코드를 살펴보자.

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


@Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어진다. 덕분에 싱글톤이 보장되는 것이다.

만약 @Configuration없이 @Bean만으로도 싱글톤이 보장될까? 아니다. @Bean은 스프링 빈으로 등록만 해줄 뿐 싱글톤을 보장해주지 않는다. 따라서 memberService()를 호출할 때마다 새로운 MemoryMemberRepository 객체를 생성해야 한다. 물론 @Autowired를 사용해 등록된 빈을 자동으로 생성자 주입시켜 싱글톤을 구현할 수 있지만,,, 굳이? 그냥 @Configuration <-- 이것만 쓰면 싱글톤 자동 보장되므로 적극 사용하도록 하자

반응형