Go, Vantage point
가까운 곳을 걷지 않고 서는 먼 곳을 갈 수 없다.
Github | https://github.com/overnew/
Blog | https://everenew.tistory.com/
티스토리 뷰
* 김영한님의 스프링 핵심원리 기본편 강좌를 수강하며 정리한 글입니다. *
이전 글에서는 빈의 설정 정보에 대해 공부해 보았다.
웹 서버에 싱글톤 패턴이 필요한 이유
스프링 애플리케이션은 대부분 웹 애플리케이션으로, 여러 클라이언트로부터 동시에 데이터를 요청받는다.
기존에 만들었던 스프링의 DI 컨테이너의 코드를 살펴보자.
자바 코드로 해석해보면, 순수 자바 DI 컨테이너는 빈이 요청될 때마다 새로운 인스턴트를 생성해서 반환을 해줄 것이다.
이런 경우 서버의 JVM 메모리에 인스턴스가 계속 생성될 것이다.
배달의 민족의 경우 초당 5만 개의 요청이 온다고 한다.
이를 직접 새로운 인스턴스로 생성해 주고 가비지 컬렉션으로 제거돼야 한다면, 서버의 빠른 응답 속도를 보장해 줄 수 없다.
따라서 클래스의 인스턴스가 단 하나만 생성되어 이를 공유하도록 설계해야 한다.
이러한 디자인 패턴이 싱글톤 패턴이다.
자세한 구현은 아래 게시글을 참조하자.
[Java] static 키워드로 만드는 싱글톤(Singleton Pattern)
그러나 효율성 뒤에는 언제나 문제가 있기 마련이다.
구체 클래스의 getInstance()와 같은 함수를 통해 접근해야 하므로, 구체 클래스에 의존하게 된다. (DIP, OCP 위반)
따라서 코드의 유연성도 떨어지므로 지양해야 하는 패턴으로도 알려져 있다.
하지만 스프링 컨테이너는 인스턴스를 모두 싱글톤으로 관리해주기 때문에 이러한 패턴을 직접 적용해줄 필요가 없을뿐더러, 언급된 문제점까지 해결을 해준다.
실제로도 스프링 컨테이너인 AppCofig에는 어떠한 싱글톤 패턴 코드가 들어가지 않았지만, 같은 인스턴스가 반환되는지 확인하는 위의 테스트는 정상적으로 수행된다.
물론 요청마다 다른 인스턴스를 생성해 반환해주는 기능도 구현 가능하다.
싱글톤 패턴의 위험성
사실 위의 문제가 해결돼도 사용을 지양하는 이유는 위험성에 있다.
싱글톤 패턴에서는 하나의 인스턴스를 모든 클라이언트가 공유하기 때문에 stateless하게 설계해야 한다.
stateless와 stateful에 대해서는 아래 게시글을 참조하자.
무상태(stateless)로 설계하기 위해서는 특정 클라이언트가 값을 수정할 수 있는 필드가 있어서는 안 된다.
즉, read-only적이어야 한다.
예를 들어 결제 시스템에 write 가능한 필드로 싱글톤 패턴을 사용한다고 가정하자.
A 사용자가 천 원을 결제하기 위해 필드 값으로 1000원을 write한 후, 결제가 진행되기 전에
B 사용자가 10만 원을 결제하기 위해 같은 필드 값에 10,0000원을 write한다.
이때 동일한 인스턴스의 필드 값을 공유하게 되면 A가 10만 원을 결제하는 심각한 문제가 발생한다.
이런 상황은 동시성 문제로도 볼 수 있을 것이다.
그러므로 스프링 빈은 무상태(stateless)로 설계 되어야 한다.
공유 필드 사용은 자제하고 지역변수, 함수 파라미터, ThreadLocal(각 쓰레드에서만 공유되는 영역 변수)을 사용하자.
@Configuration 어노테이션 내부 원리
위의 AppConfig 코드를 다시 생각해보자.
스프링 컨테이너가 싱글톤 패턴을 자동으로 적용해 주는 것은 알겠지만 자바 코드만으로 보면 다음과 같은 궁금증이 생긴다.
// memberRepository()가 여러번 호출되면 서로 다른 MemoryMemberRepository가 여러개 생성되는게 아닐까?
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
하지만 스프링 컨테이너가 반환하는 값을 확인해보면 모두 같은 인스턴스를 반환해 주고 있다.
이것을 구현해주는 것이 바로 @Configuration 이다.
@Configuration적용 시 바이트코드 변환 기술인 CGLIB가 적용되어 ,AppConfig와 같은 설정 정보 class는 AppConfig@CGLIB라는 자식 클래스가 상속하게 된다.
상속 시 부모 클래스에 선언된 메서드들을 오버라이딩 할 수 있고, 이때 메서드에 위의 static으로 만드는 싱글톤 패턴처럼 코드가 변경된다.
생성 여부를 확이 후, 되었다면 이미 생성된 스프링 빈을 컨테이너에서 찾아서 반환하고 생성되지 않았다면 새로운 빈을 생성해 컨테이너에 등록할 것이다.
만약 @Bean만 적용 시, 스프링 컨테이너로의 역할은 정상 동작하여 빈들이 등록되지만
모두 순수한 자바 코드로 적용되어 모두 다른 인스턴스를 실제로 생성하게 된다.
즉, 싱글톤 패턴이 적용되지 않는다.
'개발 > Spring' 카테고리의 다른 글
[Spring] 9. 의존 관계 주입 (0) | 2022.06.29 |
---|---|
[Spring] 8. 컴포넌트와 컴포넌트 스캔 (0) | 2022.06.28 |
[Spring] 6. 스프링 컨테이너 설정 정보와 BeanDefinition (0) | 2022.06.25 |
[Spring] 5. 스프링 빈 조회, getBean() (0) | 2022.06.25 |
[Spring] 4. 스프링 컨테이너와 스프링 빈 (0) | 2022.06.23 |