Spring 싱글톤 컨테이너

su dong·2023년 6월 9일
0

스프링 컨테이너는 싱글톤 문제점을 해결하면서 객체를 싱글톤으로 관리한다!!!(획기적인데??)
- 그게 spring bean이다?!?

진짜인지 test해보자

Spring Bean Singleton Test

AppConfig

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

Test Code

@Test
    @DisplayName("spring container 싱글톤 테스트")
    void springContainerSingletonTest() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        //1. 조회: 호출 할 때마다 객체를 생성하는지
        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).isEqualTo(memberService2);
    }

ac를 호출해주고, getbean을 해주면 같은 컨테이너를 불러오는 것을 확인할 수 있다.
(new MemberService를 해줄 필요가 없다.)

거의 대부분은 spring bean을 사용한다.

하지만 중요한 것이 있으니....!

싱글톤 방식의 주의점

여러 클라이언트가 공용해서 사용하기 때문에 stateless로 설계해야한다!
특정 클라이언트에 의존적이거나, 특정 클라이언트가 값을 변경할 수 있게 두면 안된다.
가급적 읽기만 가능해야한다.

그럼 test코드를 통해서 StatefullService의 문제점을 알아보자

StatefullService.java

public class StatefullService {

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

StatefullServiceTest.java

package hello11.core.singleton;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

import static org.junit.jupiter.api.Assertions.*;

class StatefullServiceTest {
    @Test
    @DisplayName("statefulService singleton에서 주의점")
    void statefulServiceSingleton() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(StatefullService.class);
        StatefullService statefulService1 = ac.getBean("statefullService", StatefullService.class);
        StatefullService statefulService2 = ac.getBean("statefullService", StatefullService.class);

        statefulService1.order("userA", 1000);
        statefulService2.order("userB", 2000);

        int price = statefulService1.getPrice();
        System.out.println("price = " + price);

    }

    static class TestConfig {
        @Bean
        public StatefullService statefullService() {
            return new StatefullService();
        }
    }

}

int price = statefulService1.getPrice();
System.out.println("price = " + price);
여기서 price는 얼마가 나올까??

정답은 2000원이다. 분명히 statefulService1의 userA가 주문한 가격을 조회할 목적이었지만, 공용필드값을 사용함으로써 2000원이 나온것! 이것을 정말 조심해야한다. (서비스 망한다)

즉 공용 필드 값을 설정하면 안된다.

그럼 수정해보자

StatefullService.java

package hello11.core.singleton;

public class StatefullService {

//    private int price;//상태를 유지하는 필드
    public int order(String name, int price) {
        System.out.println("name = " + name + " price = "+price);
//        this.price = price; //여기가 문제!
        return price;
    }

//    public int getPrice() {
//        return price;
//    }
}

StatefullServiceTest.java

@Test
    @DisplayName("statefulService singleton에서 주의점")
    void statefulServiceSingleton() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(StatefullService.class);
        StatefullService statefulService1 = ac.getBean("statefullService", StatefullService.class);
        StatefullService statefulService2 = ac.getBean("statefullService", StatefullService.class);

        int userA = statefulService1.order("userA", 1000);
        int userB = statefulService2.order("userB", 2000);

//        int price = statefulService1.getPrice();
        System.out.println("price = " + userA);
        System.out.println("price = " + userB);

지역변수로 return 하게 하자,
각각 1000, 2000씩 잘 나오는 것을 확인할 수 있다.

profile
사람들을 돕는 문제 해결사, 개발자 sudong입니다. 반갑습니다. tkddlsqkr21@gmail.com

0개의 댓글