Spring
[Spring] 싱글톤 패턴, 싱글톤 컨테이너
이덩우
2023. 6. 26. 18:56
- 싱글톤 패턴?
- 싱글톤 패턴이란, 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.
- 인스턴스를 2개 이상 생성하지 못하도록 막아야한다.
- private 생성자를 사용해서 외부에서 new 키워드를 사용하지 못하도록 막으면 된다. 예제 코드를 보자.
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
private SingletonService() {
}
public void logic() {
System.out.println("싱글톤 객체 로직 호출");
}
}
- 객체를 static으로 미리 하나 생성해둔다.
- 외부에서 new 키워드로 부를 수 없도록 생성자를 private으로 막아둔다.
- 이 객체 인스턴스가 필요하다면, 오직 getInstance() 메소드 호출을 통해서만 받을 수 있다.
- 항상 같은 인스턴스를 반환한다.
- 웹 애플리케이션에서 싱글톤을 적용하지 않는다면?
- 웹 애플리케이션은 보통 여러 고객이 동시에 요청을 한다. 스프링 없는 순수한 DI 컨테이너(싱글톤 x)를 생각해보자.
- 싱글톤이 아닌 순수 DI 컨테이너는 요청을 할 때마다 새로운 객체를 생성한다.
- 고객 트래픽이 초당 100이 나오면, 초당 100개의 객체가 생성되고 소멸된다 --> 메모리 낭비가 심하다!
- 해결방안은 싱글톤으로 설계하면 된다.
- 싱글톤 패턴의 문제점
- 하지만, 싱글톤 패턴이 만능은 아니다.
- 글 상단부의 코드를 다시보면 싱글톤을 구현하는 코드 자체가 많이 들어간다.
- 의존관계상 클라이언트가 구체 클래스에 의존한다. --> DIP위반
- 구체 클래스에 의존하면 OCP원칙을 위반할 가능성이 높다.
- 안티패턴으로 불리기도 한다.
- 지금까지 기껏 DIP, OCP를 지키려고 AppConfig를 만드는 과정을 거쳤다. 이제와서 다시 DIP, OCP를 위반하면 되겠나?
- 이에 대한 해답으로 싱글톤 컨테이너가 등장한다.
- 싱글톤 컨테이너의 등장 (스프링의 마법)
- 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 디폴트로 객체 인스턴스를 싱글톤으로 관리한다.
- 위에서 언급된 싱글톤 패턴의 문제점들을 해결하면서 객체 인스턴스를 단 한개로 관리하는 것이다!
- 따라서 싱글톤 패턴을 만들기 위한 지저분한 코드가 들어가지도 않고 DIP, OCP를 더 이상 신경쓰지 않고 싱글톤을 사용할 수 있다.
- 싱글톤 방식의 주의점
- 객체를 단 하나만 사용해 메모리를 줄이는 등 다양한 이점을 얻었다.
- 하지만 객체를 단 하나만 사용함으로써 생기는 주의사항들이 존재한다.
- 가령 A고객에 대한 주문정보가 B고객과 혼동되어 잘못된 정보를 전달하는 일이 발생하면 되겠는가?
- 이를 방지하기 위해서 싱글톤 객체는 항상 무상태(Stateless)로 설계해야한다.
- 무상태(Stateless)란,
1. 특정 클라이언트에 의존적인 필드가 있으면 안된다.
2. 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
3. 가급적 읽기만 가능해야한다.
4. 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야한다. - 김영한 강사님의 말씀으로, 정말 몇년에 한번씩 이 문제를 실무에서 꼭 마주친다고 한다. 항상 주의하자.
- @Configuration과 싱글톤 (바이트 코드 조작)
- 사실 @Bean만 붙인다고 스프링 컨테이너가 싱글톤으로 보장되는 것은 아니다.
- @Configuration에 마법이 있다!
- 기본적으로 AnnotationConfigApplicationContext에 파라미터로 넘긴 값 (이전 예제에서는 AppConfig.class)또한 스프링 빈으로 등록된다. 그래서 AppConfig도 스프링 빈으로 등록된다.
- @Configuration을 AppConfig 클래스에 붙여주면, 실제로 스프링 빈으로 등록될 때 원본이 등록되지 않고 CGLIB이 붙은 이름의 다른 클래스가 실제 스프링 빈으로 등록된다. 이는 사용자가 만든 클래스가 아니라, 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 원본을 상속받은 임의의 다른 클래스를 만들고 그 다른 클래스를 빈으로 등록한 것이다.
- 그 임의의 다른 클래스가 바로 싱글톤이 되도록 보장해준다. 아마도 다음과 같은 로직이 AppConfig@CGLIB내부에 있을 것이다.
@Bean
public MemberRepository memberRepository() {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) { return 스프링 컨테이너에서 찾아서 반환;
} else { //스프링 컨테이너에 없으면
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록 return 반환
}
}
- 이처럼 이미 등록된 빈을 요청한다면 새로운 객체를 생성하는게 아닌 등록된 빈을 반환해주는 동작을 해준다.
- 따라서 싱글톤이 보장된다.
- @Configuration을 빼고 @Bean만 사용해서 컨테이너에 빈을 등록하면 그때서야 AppConfig가 그 자체로 등록되고, 중복 호출에 있어서 싱글톤이 깨진다. (CGLIB가 사용이 안됨) —> 추가로 이렇게 되면 new MemoryRepository를 직접 생성해주는 자바코드를 사용한것과 다를게 없다. —> 스프링 컨테이너가 관리해주는게 아니게된다.
- AppConfig@CGLIB는 AppConfig의 자식 타입이므로, AppConfig 타입으로 조회 할 수 있다.
- 정리
- @Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다.
- 크게 고민할 필요 없이 스프링 설정 정보는 항상 @Configuration 을 사용하자!
출처 : 인프런, 김영한의 스프링 핵심 원리 - 기본편