싱글톤 패턴이란?
- 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.
- 자바가 뜰 때, SingletonPattern클래스의 static 영역에 new 가 있으니까 자기자신을 생성해서 instance변수에 넣어 놓는다.
- 조회할 때는 getInstance() 메소드를 사용해서 가져올 수 있다.
- private생성자를 이용해서 외부에서 new로 생성하는 것을 막아준다.
- 스프링 DI컨테이너는 위와 같은 싱글톤 방식으로 빈을 관리한다. 요청마다 새로운 객체를 생성하는 것이 아니라 이미 만들어진 객체를 공유해서 효율적으로 재사용할 수 있다.
싱글톤 방식의 주의점
-> 싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안된다. 무상태(stateless)로 설계해야 한다.
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
- 가급적 읽기만 가능해야 한다 (값을 수정하면 안된다)
- 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal등을 사용해야 한다.
- 스프링 빈의 필드에 공유값을 설정하면 정말 큰 장애가 발생할 수 있다..
직접 코드를 보면서 이해해보자.
이렇게 설계된 클래스가 있다고 가정해보자. order라는 메소드로 이름과 가격을 받는데 여기서 중요한건 private생성자로 int형 price가 선언되어 있음으로 인해서 상태를 유지할 수 있는 필드가 선언되어 있다는 것이다.
이 서비스 클래스의 order메소드를 사용해보자
아래 static class TestConfig는 빈으로 등록해주기 위해서 그냥 테스트용으로 간단하게 선언한 것이다.
StatefulServiceSingleton()의 중간에 보면 분명히 우리는 getBean을 사용하여 statefulService1, statefulService2를 각각 선언해서 member1과 member2가 서로 다른 가격을 주문한것으로 테스트를 하였다. 그런데 결과는...?
결과는 price1과 price2가 모두 20000원이 나온다.
이유는.. statefulService1과 statefulService2는 같은 객체이기 때문이다. 맨윗줄에서 AnnotationConfigApplicationContext(TestConfig.class)를 해줌으로써 스프링 컨테이너가 만들어진다. 스프링 컨테이너는 Bean을 디폴트로 싱글톤객체로 관리하기 때문에 statefulService1과 statefulService2는 같은 객체이다.
같은 객체를 2번 가져다가 쓰니까 member2를 order할때 중간에 끼어드는 형태가 되어버린 것이고 결론적으로 price1과 price2는 20000으로 같게 나와버렸다.
이 문제를 해결하기 위해서 위에 설명한대로 우리는 스프링 빈을 무상태(stateless)로 설계를 해야한다. 즉, 특정 클라이언트에 의존적인 필드가 있게 설계하면 안된다는 뜻이다.
StatefulService클래스를 고쳐보자.
order 메소드를 int형으로 수정하고 상태를 유지하는 필드를 사용하는 것이 아니라 메소드 안에서 바로 return을 해주는 식으로 변경하였다. price를 얻기 위해 만들었던 getPrice메소드도 삭제하였다.
테스트 코드도 이렇게 바꾸어서 찍어보면 우리가 원하는대로 price1과 price2는 각각 member1과 member2의 가격이 나온다.
@Configuration 과 @Bean
-> @Bean만 사용해도 스프링 빈으로 등록이 되기는 하지만, 싱글톤이 보장이 되지 않는다. (의존관계 주입이 필요해서 메서드를 직접 호출할 때 싱글톤이 보장이 되지 않는다는 의미)
-> @Bean으로 등록한 빈은 자바코드로 return new 구현클래스(); 와 같은 방식으로 되어 있어서 여러번 이 객체를 사용하면 매번 다른 객체가 호출되어야 할 것 같지만 이상하게도 싱글톤을 보장한다.. 왜?!
-> @Configuration과 @Bean을 사용하여 스프링빈을 등록하면 (예를 들어 AppConfig 클래스가 스프링빈으로 등록되었다고 한다면,) 스프링 컨테이너에는 바로 내가만든 AppConfig를 등록하는 것이 아니라 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig를 상속받은 임의의 다른 클래스를 만들어버리고 그 다른 클래스를 빈으로 등록한다.
(ex. AppConfig$$CGLIB$$bd23232)
-> AppConfig@CGLIB는 호출한 빈이 스프링 컨테이너에 등록이 되어있으면 스프링컨테이너에서 찾아서 반환하고 그렇지 않으면 기존 로직을 호출해서 빈을 생성하고 컨테이너에 등록 하는 과정이 코드로 정의가 되어있을 것이다. 이러한 과정에서 등록되어있는 빈은 새로 만드는 것이 아니라 등록되어있는 것을 찾기 때문에 싱글톤이 보장이 되는 것이다.
'JAVA > Spring' 카테고리의 다른 글
의존관계 자동주입 (0) | 2021.12.17 |
---|---|
컴포넌트 스캔 (@ComponentScan) (0) | 2021.12.17 |
Spring Socket에 대해 알아보자! - 2. WebSocket 만들어보자 (0) | 2020.06.23 |
Spring Socket에 대해 알아보자! - 1. WebSocket (0) | 2020.06.18 |
Spring WebSocket에 대한 내용 정리 (0) | 2020.06.11 |