Home 싱글톤 컨테이너
Post
Cancel

싱글톤 컨테이너

스프링은 태생이 기업용 온라인 서비스 기술을 지원하기 위해 탄생했다.

대부분의 스프링 애플리케이션은 웹 애플리케이션이다. 물론 웹이 아닌 애플리케이션 개발도 얼마든지 개발할 수 있다.

웹 애플리케이션은 보통 여러 고객이 동시에 요청을 한다.

이렇게 고객3명이 요청을 하면 객체 3개를 생성해야 한다.

고객 트래픽이 초당 100이 나오면 초당 100개 객체가 생성되고 소멸된다. -> 메모리 낭비가 심하다.

=> 해당 객체가 하나만 생성되도록 하고 공유하도록 설계하면 된다.

싱글톤 패턴

객체 인스턴스를 2개 이상 생성하지 못하도록 막아야 한다.

  • private 생성자를 이용하여 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야 한다.
1
2
3
4
5
6
7
8
9
10
public class SingletonService{
	private static final SingletonService instance = new SingletonService();
	
	private SingletonService(){
	} //생성자를 private으로 해놓음으로써 외부의 new를 방지.
	
	public static SingletonService getInstance(){
		return instance;
	}
}

문제점

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

싱글톤 컨테이너

스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다. 스프링 빈이 바로 싱글톤으로 관리되는 빈이다.

  • 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다.
  • DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있다.

스프링 컨테이너 덕분에 고객의 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 재사용할 수 있다.

** 기본 빈 등록 방식은 싱글톤이지만 요청할 때 마다 새로운 객체를 생성해 반환하는 방식도 지원한다. (99%는 싱글톤만 사용함.)

☆싱글톤 방식의 주의점

싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 상태를 유지하도록 설계하면 안된다.

  • 무상태(Stateless)로 설계해야 한다.
    • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
    • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
    • 가급적 읽기만 가능해야 한다.
    • 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.

상태를 유지할 경우 발생하는 문제점 예시

1
2
3
4
5
6
7
8
9
10
11
12
public class StateFulService {  
	private int price; //상태를 유지하는 필드  
  
	public void order(String name, int price){  
		System.out.println("name= " + name + " price= " + price);  
		this.price = price; //여기가 문제! 
	}  
  
	public int getPrice(){  
		return price;  
	}  
}

사용자1이 만원 주문을 넣은 상태에서 사용자 2가 2만원 주문을 넣으면 price가 2만원으로 바뀌는 문제가 발생함. 사용자1이 조회하면 만원이 나와야 하는데 2만원이 나오는 상황이 발생.

  • 무상태
1
2
3
4
5
6
public class StateFulService {   
	public int order(String name, int price){  
		System.out.println("name= " + name + " price= " + price);  
		return price;
	}  
}

** 진짜 공유필드는 조심해야한다! 스프링 빈은 항상 무상태로 설계하자.

@Configuration과 싱글톤

1
2
3
4
5
6
7
8
9
@Bean
public MemberService memberService(){
	return new MemberServiceImpl(memberRepository());
}

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

이 상황에서 memberRepository() 두 번 불려서 new MemoryMemberRepository() 두번 불리는데 싱글톤 깨지는거 아니야? 라는 의심이 듦.

스프링 컨테이너는 이 문제를 어떻게 해결할까?

테스트를 해보면 동일한 인스턴스를 공유한다.

혹시 두 번 호출을 하지 않는 것이 아닐까?

-> 호출을 실제로 한번씩밖에 안함.

@Configuration과 바이트코드 조작의 마법

호출을 한번씩밖에 안하는 비밀 -> @Configuration

스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용하여 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것.

그 임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다.

  • AppConfig@CGLIB 간단 예상 코드
1
2
3
4
5
6
7
8
9
10
@Bean
public MemberRepository memberRepository(){
	if(memoryMemberRepository가 이미 스프링 컨테이너에 등록되어있으면?){
		return 스프링 컨테이너에서 찾아 반환;
	}
	else{
		기존 로직을 호출해서 MemoryRepo 생성하고 스프링컨테이너에 등록
		return 반환
	}
}

@Configuration을 적용하지 않고, @Bean만 적용하면?

AppConfig가 CGLIB 적용되지 않고 순수하게 빈으로 등록되는 것을 알 수 있다.

-> 싱글톤이 깨진다.

즉 @Bean만 사용해도 스프링 빈에 등록은 되지만 @Configuration을 적용하지 않으면 싱글톤을 보장해주지 않는다.

memberRepoistory() 처럼 의존관계 주입이 필요해서 메서드를 직접 호출할 때 싱글톤을 보장해주지 않으므로

크게 고민할 것 없이 스프링 설정 정보는 항상 @Configuration을 이용하자.

This post is licensed under CC BY 4.0 by the author.