우리는 스프링 컨테이너에 저장된 빈들을 가져와서 사용할 수 있다.
하지만 만약 빈들을 가져올 때마다 new 연산자를 통해 객체를 새로 생성해서 가져올 경우 여러 비효율적인 문제가 있을 수 있다.
이와 같은 문제를 해결하기 위해 스프링 컨테이너는 객체 인스턴스를 싱글톤으로 관리한다.
싱글톤 패턴
싱글톤 패턴이란 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.
구현방법은 다음과 같다.
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
private SingletonService() {
}
}
1. static 영역에 객체 인스턴스를 미리 생성해서 올려둔다.
2. 객체 인스턴스가 필요한 경우 오직 getInstance()를 통해서만 조회가 가능하고 항상 같은 인스턴스를 반환한다.
3. 1개의 인스턴스만 존재해야 하기 때문에, 생성자를 private로 막아 외부에서 new 키워드로 객체 인스턴스가 생성되는 것을 막는다.
싱글톤 패턴을 사용할 경우 클래스의 인스턴스를 하나만 생성하게 하는 장점이 있지만 문제점도 존재한다.
1. 싱글톤 패턴의 구현코드 자체가 많이 들어간다.
2. 클라이언트가 구체 클래스에 의존한다. -> DIP 위반
3. 클라이언트가 구체 클래스에 의존하여 OCP를 위반할 가능성이 높다.
싱글톤 컨테이너
스프링 컨테이너는 객체 인스턴스를 싱글톤으로 관리하여 싱글톤 컨테이너 역할을 한다.
하지만 위와 같은 싱글톤 패턴을 적용하지 않고 관리하기 때문에 싱글톤 패턴의 모든 단점을 해결하면서 객체를 싱글톤으로 유지할 수 있다.
고객의 요청이 올 때마다 새로운 객체를 생성하는 것이 아닌, 이미 만들어진 객체를 공유하여 효율적으로 재사용할 수 있다.
그렇다면 빈들을 싱글톤으로 등록하기 위해서는 어떻게 해야 할까?
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
앞에서 사용하였던 AppConfig를 통해 살펴보겠다.
@Configuration을 설정 정보 클래스에 붙이고 빈으로 등록하고 싶은 코드에 @Bean을 사용하였다.
이런 방식으로 사용하면 스프링 컨테이너가 자동으로 싱글톤으로 관리하게 된다.
하지만 @Configuration이 없이 @Bean만 사용하게 된다면 스프링 빈으로 등록이 되지만 싱글톤을 보장하진 않는다.
즉, 스프링 설정정보에는 @Configuration을 붙여서 사용해야 한다.
싱글톤 방식의 주의점
싱글톤 방식을 사용할 때 여러 클라이언트들은 하나의 객체 인스턴스를 공유한다.
만약 싱글톤 객체가 상태를 유지하게 되는 경우 예상치 못한 결과를 얻을 수 있다.
public class StatefulService {
private int price; //상태를 유지하는 필드
public void order(int price) {
this.price = price; //문제 지점
}
public int getPrice() {
return price;
}
}
위의 클래스에는 price라는 상태를 저장하는 필드가 있다.
만약 싱글톤 방식을 사용하는 경우 어떠한 문제가 발생할 수 있을까?
싱글톤 방식을 사용하여 statefulService1, statefulService2를 생성했다고 가정해 보자.
statefulService1.order(10000);
statefulService2.order(20000);
int price = statefulService1.getPrice();
각각의 order 메서드는 price의 값을 변경하는데, 하나의 객체 인스턴스를 공유하기 때문에 가장 나중의 값인 20000이 price에 저장된 상태가 된다.
따라서 statefulService1의 price 값이 20000으로 저장되는 결과가 나오게 된다.
public class StatefulService {
//private int price; //상태를 유지하는 필드
public int order(int price) {
//this.price = price; //문제 지점
return price;
}
}
따라서 싱글톤 객체는 위와 같이 무상태로 설계하여 변경될 여지가 없게 만들어야 한다.
'Spring' 카테고리의 다른 글
[Spring] 의존관계 자동 주입 (0) | 2022.12.27 |
---|---|
[Spring] 컴포넌트 스캔 (0) | 2022.12.27 |
[Spring] 스프링 컨테이너와 스프링 빈 (0) | 2022.12.26 |
[Spring] AppConfig를 이용한 의존관계 주입 (0) | 2022.12.23 |
[Spring] 객체 지향 5가지 원칙(SOLID) (0) | 2022.12.21 |