싱글톤 컨테이너

 

스프링 컨테이너는 싱글톤 패턴의 문제점을 해결해주며 객체 인스턴스를 싱글톤으로 관리한다. 

 

public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    private SingletonService(){
    }

    public static SingletonService getInstance(){
        return instance;
    }
}

 

또한 위 코드처럼 지저분한 싱글톤 패턴코드를 쓰지않고도 스프링 컨테이너는 객체를 하나만 생성해서 싱글톤으로 관리한다.

 

 

 

 

[스프링 컨테이너를 이용해 싱글톤 패턴 테스트]

@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    
    //1. 조회: 호출할 때 마다 같은 객체를 반환
    MemberService memberService1 = ac.getBean("memberService", 
    MemberService.class);
    
    //2. 조회: 호출할 때 마다 같은 객체를 반환
    MemberService memberService2 = ac.getBean("memberService", 
    MemberService.class);
    
    //참조값이 같은 것을 확인
    System.out.println("memberService1 = " + memberService1);
    System.out.println("memberService2 = " + memberService2);
    
    //memberService1 == memberService2
    assertThat(memberService1).isSameAs(memberService2);
}

 

 

 

[결과] 

객체 같음 

 

 

싱글톤 컨테이너를 적용하면 

고객의 요청이 올때마다 객체를 생성하는것이 아닌 이미 만들어진 객체를 공유해서 재사용할 수 있다. 

스프링 컨테이너를 사용하면 싱글톤으로 동작한다고 이해하면 된다. 

 

하지만 싱글톤 방식만 지원하는것은 아니고 요청할때마다 새로운 객체를 생성해서 반환하기도 한다 

 

 

 

 

 

싱글톤 방식의 주의점

 

 

 

하나의 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지하게 설계하면 안된다

특정 클라이언트가 값을 변경할 수 있으면 안된다

무상태로 설계해야한다 

 

만약 상태를 유지하는 경우 값을 변경하는 경우

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;
    }
}

 

StatefulService 클래스의 price 변수가 아래 코드에는 공유되는 필드가 되는데

클라이언트가 값을 변경시켜서 문제가 된다. 

 

 

public class StatefulServiceTest {
    @Test
    void statefulServiceSingleton() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

        StatefulService statefulService1 = ac.getBean("statefulService",StatefulService.class);
        StatefulService statefulService2 = ac.getBean("statefulService",StatefulService.class);

        //ThreadA: A사용자 10000원 주문
        statefulService1.order("userA", 10000);

       //ThreadB: B사용자 20000원 주문
        statefulService2.order("userB", 20000);

       //ThreadA: 사용자A 주문 금액 조회
        int price = statefulService1.getPrice();

       //ThreadA: 사용자A는 10000원을 기대했지만, 기대와 다르게 20000원 출력
        System.out.println("price = " + price);

        Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
    }
    
    static class TestConfig {
        @Bean
        public StatefulService statefulService() {
      	  return new StatefulService();
        }
    }
}

 

 

statefulService1.order("userA", 10000); 다음에는 statefulService2.order("userB", 20000); 가 있으면 

statefulService2.order("userB", 20000); 가 저장이 되기 때문에 price값은 20000원으로 출력이 된다. 

 

이렇게 공유필드는 조심해야해서 스프링 빈은 항상 무상태로 설계해야한다. 

 

 

 

 

 

@Configuration

 

싱글톤을 위해 존재하는 @Configuration 

@Bean 만 사용하면 스프링 빈으로 등록은 되지만 싱글톤 보장은 하지않기 때문에 

 

스프링 설정정보에는 무조건 @Configuration을 써야 싱글톤을 보장해준다. 

@Configuration
public class AppConfig {

	@Bean
	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository());
	}
	
    .....
 }

 

 

 

반응형
LIST

+ Recent posts