[김영한님의 스프링 핵심 원리-기본편을 학습 후 정리한 내용입니다.]
싱글톤이란?
웹 어플리케이션은 많은 고객들의 무수한 요청에 대해서 응답을 해야한다. 고객들의 요청마다 Bean의 생성자가 호출된다면,, 10000명의 고객이라고 했을 때 10000개 혹은 그 이상의 객체가 생성된다. 즉, 메모리 낭비가 필수불가결하다. 바로 여기서 등장한 것이 싱글톤이라는 개념이다. 싱글톤은 말 그대로 여러 생성자가 호출되더라도 1개의 객체만을 생성해서 공유하는 디자인 패턴을 뜻한다.
싱글톤 패턴으로 만드려면 어떻게 해야할까?
//샘플코드
package hello.core.singleton;
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance(){
return instance;
}
private SingletonService(){
};
}
위의 샘플코드처럼 static영역에 SingletonService 객체 instance를 생성해서 올려둔 다음, 이 인스턴스를 꺼낼 때는 getInstance()메서드를 통해서만 조회하도록 설계한다.
동시에 딱 1개의 객체 인스턴스만 존재해야 하므로, 위와 같이 private으로 생성자를 막아서 혹시라도 외부에 new로 객체 인스턴스가 생성되는 것을 막아줘야 한다. 이렇게 되면 호출할 때마다 같은 객체인스턴스를 반환하는 것을 확인 가능하다.
이렇게 잘 설계한 싱글톤 패턴에도 문제점이 있다.
- 싱글톤 패턴을 구현하는 코드가 많이 들어간다.
- 클라이언트가 구현클래스에 의존한다.(DIP위반)
- private생성자로, 자식클래스를 만들기 어렵다.
- 유연성이 떨어진다 등..
싱글톤 컨테이너
스프링컨테이너는 이러한 싱글톤 패턴의 문제점을 해결하면서 객체인스턴스를 1개로 관리한다. 우리가 학습했던 Bean이 바로 싱글톤으로 관리된다. 아래 코드와 같이 스프링 컨테이너를 사용해서 호출할 때마다 같은 객체를 반환하는지 테스트해보면 같은 객체를 공유하고 있다는 것을 알 수 있다.(참고로 설정파일Appconfig에는 MemberServiceImpl을 반환하는 memberService가 정의되어 있다.)
@Test
@DisplayName("스프링링 컨테이너와 싱글톤")
void springContainer(){
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);
Assertions.assertThat(memberService1).isSameAs(memberService2);
}
간단한 테스트를 통해서 같은 객체를 공유하고 있음을 확인했다. 그런데 여기서 의문점이 한가지 드는데, 아래의 Appconfig클래스를 살펴보자.
@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();
}
@Bean
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
}
}
- memberService가 빈에 등록되면 memberRepository()가 호출되어서 MemoryMemberRepository()가 호출된다.
- orderService가 빈에 등록되면 memberRepository()가 호출되어서 MemoryMemberRepository()가 호출된다.
즉, 결과적으로 각각 다른 2개의 MemoryMemberRepository가 생성되면서 싱글톤이 깨지는 것처럼 보인다. 그럼 memberService에서 생성된 memberRepository와, orderService에서 생성된 memberRepository와, Bean에 직접 등록된 memberRepository가 같은 객체인지 한번 테스트해보자.
@Test
void ConfigurationTest(){
ApplicationContext ac = new AnnotationConfigApplicationContext(Appconfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
//모두 같은 인스턴스 참조
System.out.println("memberService -> memberRepository = " + memberService.getMemberRepository());
System.out.println("orderService -> memberRepository = " + orderService.getMemberRepository());
System.out.println("memberRepository = " + memberRepository);
Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
}
결과는 역시,, 하나의 객체를 공유하고 있다. 의문점까지 해결되면서 스프링컨테이너는 완벽하게 싱글톤을 지원하고 있다는 결론을 낼 수 있다.
@Configuration의 역할
@Configuration의 역할이 무엇인지 알기 위해서 Appconfig설정파일에 @Configuration 어노테이션을 지우고 바로 직전의 테스트를 재시작해보자.
결과는.. 다른객체가 생성되었다. 즉, @Configuration이 없어도 Bean에 등록되긴 하지만 싱글톤은 보장해주지 못한다는 것을 알 수 있다.
사실 @Configuration은 바이트코드를 조작하는 CGLIB라이브러리를 사용하게 함으로써 싱글톤을 보장해준다.
이 말이 무슨말이냐면,, 우리가 만든 설정파일 AppConfig을 Bean에 등록하면 AppConfig가 그대로 등록되지 않고 스프링자체에서 AppConfig클래스를 상속받은 임의의 다른 클래스를 만들고, 그 클래스를 Bean으로 등록한다. 그 다른 클래스에서는, Bean이 이미 스프링 컨테이너에 등록되어 있으면 스프링컨테이너에서 찾아서 반환해주고, 만약 스프링 컨테이너에 등록되어 있지 않은 Bean이라면 새로 스프링컨테이너에 등록해주는 로직이 있다.
바로 이러한 로직 덕분에 싱글톤이 보장된다..!!
끝으로..
이렇게 스프링같은 싱글톤 컨테이너를 사용할 때 객체 인스턴스 1개만 생성해서 공유하는 방식은, 여러 고객이 하나의 같은 객체인스턴스를 공유하기 때문에 꼭 stateless하게 설계해야 한다. 즉, 고객이 값을 변경할 수 있는 필드가 있으면 안된다. 특정 고객에 의존적인 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터 등을 사용해야 한다.
'Frameworks > Springboot' 카테고리의 다른 글
[Springboot] 의존관계 자동 주입 방법 정리 (0) | 2022.07.28 |
---|---|
[Springboot] 컴포넌트 스캔과 의존관계 자동주입 정리 (0) | 2022.07.25 |
[Springboot] 스프링 컨테이너 & 스프링 빈 개념 정리 (0) | 2022.07.23 |
[Springboot] 객체 지향 설계를 위한 AppConfig 구성 (0) | 2022.07.22 |
[Springboot] 좋은 객체 지향 프로그래밍과 스프링 (0) | 2022.07.20 |
댓글