Spring ‐ 싱글톤 컨테이너 - dnwls16071/Backend_Study_TIL GitHub Wiki
📚 싱글톤 패턴
- 클래스의 인스턴스가 오직 1개만 생성되는 것을 보장하는 디자인 패턴이다.
- 객체 인스턴스를 2개 이상 생성하지 못하도록 막아야 한다. 그래서 생성자를
private
접근 제어자로 지정해 외부에서 임의로 객체를 생성할 수 없도록 막아야 한다.
public class SingletonService {
// 메서드 영역에 하나의 인스턴스가 만들어진다.(힙 영역 X)
private static final SingletonService INSTANCE = new SingletonService();
// 메서드 영역에 떠 있는 인스턴스 객체를 조회한다.
public static SingletonService getInstance() {
return INSTANCE;
}
// 외부에서 new 키워드로 생성자를 호출할 수 없도록 private 접근 제어자로 막는다.
private SingletonService() {
}
public void logic() {
System.out.println("싱글톤 객체의 로직 호출");
}
}
테스트 코드 실행 결과
- 싱글톤 패턴의 단점
- 위와 같이 접근 제어자로 생성자를 제한하는 등 싱글톤 패턴을 구현하는 코드 자체가 많다.
- 의존관계상 클라이언트가 구체 클래스에 의존한다. 결국 적절한 추상화를 사용하지 못하므로 DIP를 위반하게 된다.
- 테스트하기가 어렵다.
- 내부 속성을 변경하거나 초기화하기 어렵다.
- 유연성이 떨어진다.
📚 싱글톤 컨테이너
- 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다.
- 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다. 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라고 한다.
- 고객의 요청이 올 때마다 객체를 생성하는 것이 아니라 아래와 같이 이미 만들어진 객체를 공유해서 객체를 재사용할 수 있다.
📚 싱글톤 방식의 주의점
- 싱글톤 패턴은 객체 인스턴스를 하나만 공유해서 사용하기 때문에 상태를 유지하는 설계를 취해서는 안 된다.
- 무상태로 설계해야 한다. → 특정 클라이언트에 의존적인 필드가 있으면 안되고 특정 클라이언트가 값을 변경할 수 있는 필드가 있어선 안 된다.
- 필드 대신에 자바에서 공유되지 않는 스택 영역의 지역 변수나 파라미터, 쓰레드 로컬 등을 사용해야 한다.
public class StatefulService {
private int price; // 필드 공유
public void order(String name, int price) {
System.out.println("상태가 있는(stateful) 서비스: " + name + " - " + price);
this.price = price;
}
public int getPrice() {
return price;
}
}
public class StatefulServiceTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonConfig.class);
@Test
@DisplayName("싱글톤은 상태를 보존하는 설계를 하면 장애가 발생한다.")
void singletonStatefulService() {
StatefulService statefulService1 = ac.getBean("statefulService", StatefulService.class);
StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);
statefulService1.order("userA", 1000);
statefulService2.order("userB", 2000);
int price = statefulService1.getPrice();
assertThat(price).isEqualTo(1000);
}
@Configuration
static class SingletonConfig {
@Bean
public StatefulService statefulService() {
return new StatefulService();
}
}
}
테스트 코드 실행 결과
상태가 있는(stateful) 서비스: userA - 1000
상태가 있는(stateful) 서비스: userB - 2000
Expected :1000
Actual :2000
- userA가 주문한 금액을 조회해서 나오길 기대했는데 userB가 주문한 금액이 조회해서 나오는 것을 볼 수 있다.
- 아래와 같이 금액 필드를 공유하지 않도록 price를 파라미터로 사용하면 공유되는 문제를 해결할 수 있다.
public class StatefulService {
// price를 파라미터로 사용하기
public int order(String name, int price) {
System.out.println("상태가 있는(stateful) 서비스: " + name + " - " + price);
return price;
}
}
📚 @Configuration과 싱글톤
- 구성 파일에
@Configuration
어노테이션을 붙이면 포함된 메서드에 붙어 있는@Bean
을 호출해서 스프링 빈을 생성한다. - CGLIB라는 바이트 코드 조작 라이브러리를 통해 구성 파일 클래스를 상속받은 임의의 다른 클래스를 만들고 그 클래스를 스프링 빈으로 등록하여 싱글톤으로 등록하는 것이다.
- 만약에
@Bean
만 사용하게 되면 싱글톤으로 관리되지 않는다. - 설정 정보는 항상
@Configuration
&@Bean
을 같이 사용해 싱글톤으로 관리하자.