-
웹 어플리케이션은 보통 여러 고객들이 동시에 요청을 한다.
생각을 해보자. 클라이언트 A,B,C가 동시에 memberService를 요청하게되면 memberService를 3번이나 반환해야 된다. 즉, 객체가 바로 3개가 생성이 된다.
다시말해 고객이 요청할때마다 객체를 생성하는 부분이 문제이다.
service요청1,2번이 각기 다른 객체이다.
- 고객 트래픽이 초당 100이 나오면 초당 100개 객체가 생성되고 소멸된다
메모리 낭비가 심하다.
- 해결방안은 해당 객체가 딱 1개만 생성되고, 공유하도록 설계하면 된다.
싱글톤 패턴
싱글톤
: 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
- 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
- 의존관계상 클라이언트가 구체 클래스에 의존한다. DIP를 위반한다.
- 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
- 테스트하기 어렵다.
- 내부 속성을 변경하거나 초기화 하기 어렵다.
- private 생성자로 자식 클래스를 만들기 어렵다.
- 결론적으로 유연성이 떨어진다.
스프링 컨테이너는 이러한 싱글톤의 문제점들을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다.
싱글톤 컨테이너
- 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
- 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다.
- 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라고 한다.
- DIP, OCP, private 생성자로부터 자유롭게 싱글톤을 유지할 수 있다.
스프링을 이용하여 객체를 생성해보면 보시다시피 같은 객체로 확인이 된다. 따라서 스프링 컨테이너로 고객의 요청이 올때마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 재사용할 수 있다.
싱글톤 방식의 주의점
싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안된다.
즉 무상태로 설계를 해야한다.
특정 클라이언트에 의존적인 필드가 있으면 안된다.
특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
가급적 읽기만 가능해야한다.(값을 수정하면 안된다)
필드대신에 자바에서 공유되지 않는, 지역변수, 파라미터 등을 사용해야한다.
public void statefulServiceSingleton(){ ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class); StatefulService statefulService1 = ac.getBean(StatefulService.class); StatefulService statefulService2 = ac.getBean(StatefulService.class); statefulService1.order("userA", 10000); statefulService2.order("userB", 20000); int price = statefulService1.getPrice(); System.out.println(price); } static class TestConfig{ @Bean public StatefulService statefulService(){ return new StatefulService(); } }
userA가 10000원을 주문을 했고, 주문 가격을 출력하려는 사이에 userB가 다른 주문을 했다고 가정을 하자.
우리가 기대하는 값은 userA가 주문한 값인 10000원 인데
20000원이 나온다. 둘다 같은 인스턴스를 사용하기 때문이다.
따라서,,, 공유되는 필드는 정말 조심해야한다.
스프링 빈은 항상 stateless로 설계해야한다.
여러 쓰레드를 생성하고 관리할때 싱크로나이즈를 유의하면서 사용해야하는 이유와 같다.public class StatefulService { //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; // } }
따라서 필드의 값을 차라리 없애고 바로 넘겨버리는 식으로 설계를 하면 이런 문제를 해결할 수 있다.(물론 사용하려는 코드도 수정해야한다.)
Configuration
@Configuration public class AppConfig { @Bean public MemberService memberService(){ return new MemberServiceImpl(memberRepository()); } @Bean public MemberRepository memberRepository() { return new MemoryMemberRepository(); } @Bean public OrderService orderService(){ return new OrderServiceImpl(memberRepository(), discountPolicy()); } @Bean public DiscountPolicy discountPolicy(){ return new RateDiscountPolicy(); } }
memberService bean을 만드는 코드를 보면 memberRepository()호출 -> new MemoryMemberRepository()를 호출한다. orderService bean을 만드는 코드를 보면 memberRepository()호출 -> new MemoryMemberRepository()를 호출,
discountPolicy()를 호출한다.
결과적으로보면 각각 다른 2개의 MemoryMemberRepository가 생성되면서 싱글톤이 깨지는것 같지만 아니다.
놀랍게도 같은 객체이다.
AppConfig를 스프링 빈으로 조회를 해보면
클래스 명에 xxxCGLIB가 붙으면서 상당히 복잡해진 것을 볼 수 있다. 이것은 내가 만든 클래스가 아니라 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 바이트코드로 조작한 클래스를 스프링 빈으로 등록한 것이다.
CGLIB을 이용하여 다른 클래스를 하나 만들고, CGLIB으로 조작한 클래스로 스프링 빈에 등록한다.
따라서 컨테이너에는 조작된 클래스가 들어가게 되고, 이것이 싱글톤이 보장되도록 보장해준다.@Bean이 붙은 메소드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 스프링 빈이 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어진다!!
덕분에 싱글톤이 보장이 된다.
@Configuration이 없이 @Bean만 한다면 CGLIB기술이 없이 순수한 AppConfig로 스프링 빈에 등록이 되고,,,, 객체를 생성할때다마 bean으로 등록을 한다. 결국,,, 싱글톤이 깨진다..
728x90'스프링' 카테고리의 다른 글
의존관계 자동주입 (2) 2022.12.22 스프링 컨테이너와 빈 (0) 2022.12.22 IOC,DI,컨테이너 (0) 2022.12.19 빈 등록 (0) 2022.12.19 의존관계에 대한 이해 (0) 2022.12.19