본문 바로가기

Spring/기본

싱글톤 (웹과 관련하여)

1. 스프링과 비교

  • 웹 어플리케이션은 보통 여러 고객이 동시에 요청
  • 스프링없는 순수한 DI 컨테이너는 AppConfig 요청을 할떄마다 객체 생성.
  • 객체 인스턴스를 생성하는 비용이 1000 이라면 가져오는 비용은 1정도로 차이가 큼.

 

2. 싱글톤 패턴의 문제점

  • 싱글톤 패턴 구현 코드 많이 들어감.
  • 의존 관계상 클라이언트가 구체 클래스에 의존(DIP 위반)
  • 클라이언트가 구체 클래스에 의존해서 OCP위반 가능성 높음.
  • 테스트  하기 어려움.
  • 내부 속성을 변경ㅎ거나 초기화 하기 어렵다. 
  • private생성자로 자식 클래스를 만들기 어렵다. 
  • 결론적으로 유연성 떨어진다. 
  • 안티패턴으로 불리기도 한다. 

 

3. 싱글톤 컨테이너

  • 스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다. 
    • 스프링 컨테이너는 컨테이너 객체를 하나만 생성해서 관리. 
    • ==> 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다. 
    • 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라고 한다. 
    • 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 되고 DIP OCP, 테스트, private생성자로 부터 자유롭게 싱글톤 사용 가능.

4. 싱글톤 방식의 주의점

  • 싱글톤 패턴이든 싱글톤 컨테이너이든 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지하기(stateful)하게 설계하면 안된다
  • stateless로 설계 해야 된다.
    • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
    • 특정 클라이언트가 값을 변경할 수 있으면 안된다.
    • 가급적 읽기만 가능해야 한다.
    • 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, TreadLocal등을 사용해야 한다.
  • ***스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애 발생 가능!!
public class StatefulService{
	
    private int price; 
    
    public void order(String name, int price){
    System.out.rptintln("name = " + name + "price = " + price);
    this.price = price; //문제 부분
}

}

//변경!

public class StateLessService{
	
    //private int price; 
    
    public int order(String name, int price){
    System.out.rptintln("name = " + name + "price = " + price);
    //this.price = price; //문제 부분
	return price;
}

}

 

  • price필드는 공유되는 필드 인데, 특정 클라이언트가 값을 변경. (공유 필드 조심!!! 스프링 빈은 항상 무상태로 설계) 

5. AppConfig관련 이슈

  • @Confiugration없이 @Bean만 있으면 Bean등록 되지만 싱글톤 보장하지는 않음. (바이트 코드 조작 없이 각각 호출)
    • 또한 스프링 컨테이너에서  관리도 되지 않는다. (그저 의존성 주입만 해준 걸까..?)
    • AppConfig가 사용하는 인스턴스에 @Autowired를 붙이면 스프링 컨테이너에서 끌어 오지만 권장하는 사용법은 아님. 
  • 같은 객체를 AppConfig에서 여러번 호출하는 경우가 생기는데 이때 싱글톤이 깨지는게 아닐까?
    • 바이트 코드를 조작하여 AppConfig를 상속받은 임의의 다른 클래스를 만들어 스프링 빈에 등록 (@CGLIB)
    • 이 글래스가 싱글톤 보장 
//예상

@Bean
public MemberRepository memberRepository(){

	if(memoryMemberRepository가 이미 스프링 컨테이너에 등록됨?){
    	return 스프링 컨테이너에서 찾아서 반환;
    }else{
    	기존 로직을 호출해서 MemoryMemberRepository생성하고 스프링 컨테이너에 등록
    	return 반환
    }	
}

 

- 조회 코드

  • AppConfig@CGLIB는 AppConfig의 자식 클래스라서 조회 됨
@Test
void configurationDeep(){
	ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    AppConfig bean = ac.getean(AppConfig.class);
    
    System.out.println("bean = " + bean.getClass());
}

'Spring > 기본' 카테고리의 다른 글

컴포넌트 스캔  (2) 2024.01.17
의존성  (1) 2024.01.16