티스토리 뷰
*웹 어플리케이션에서 클라이언트의 요청에 따른 객체 생성이 빈번히 일어나면 공간 상, 성능 상 큰 제약이 따를 수 있다. 예를 들면 다음과 같이 순수 자바 코드로 AppConfig를 구성하는 경우이다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(
new MemoryMemberRepository(),
new FixDiscountPolicy());
}
}
AppConfig.memberSerivce.~ 의 형태로 여러 클라이언트가 memberService를 호출하면 서로 다른 MemberServiceImpl 객체와 그에 관련되어 있는 객체들까지(위에선 MemoryMemberRepository) 모두 포함해서 계속 생성되고 자바 GC의 작동과 메모리 공간 상 낭비로 이어진다.
*이러한 단점을 해결하기 위해 차용한 것이 싱글톤 패턴이다.
(싱글톤 패턴: 소프트웨어 디자인 패턴 중 하나로, 클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 패턴)
싱글톤 패턴을 구성하는 자바 코드는 다음과 같음.
package hello.core.singleton;
public class SingletonService {
//static으로 static 영역에 띄워놓기
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
//private 접근 제한자로 외부에서의 객체 생성 제한
private SingletonService() {
}
public void logic() {
System.out.println("싱글톤 객체 로직 호출");
}
}
*싱글톤 패턴의 단점
- 클라이언트가 구현체에 의존해서 DIP원칙에 위반되고 OCP 원칙도 위반. (ex) 위 코드에서 SingletoneService에 의존하고 있음)
- 싱글톤 패턴 구현에 필요한 코드가 많음.
- 등등의 이유로 유연성이 매우 떨어진다.
*이러한 싱글톤 패턴의 단점을 모두 해결해주는 스프링 DI 컨테이너
-스프링 빈의 기본 등록 방식은 싱글톤 패턴으로 구현되어 있다.(다른 방식으로 바꿀 수도 있음)
//앱 구성을 스프링으로 관리하기 위한 @Configuration
@Configuration
public class AppConfig {
//스프링 컨테이너에 객체들을 관리 하기 위해 @Bean으로 등록
//스프링 컨테이너에 등록된 객체들을 '스프링 빈' 이라고 한다
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
...
}
위와 같이 AppConfig를 구성하면 memberService 객체는 하나만 등록되어 여러 클라이언트들이 이를 공유해서 사용하게 된다. 즉 같은 참조를 하게 된다. 또한 싱글톤 패턴을 코드로 구현할 필요가 없어진다.
단! 싱글톤 패턴을 이용한 스프링 빈의 특성 상 이를 사용할 때 주의할 점이 있다.
---> 한 객체를 여러 클라이언트가 공유하기 때문에, 공유되는 객체 클래스에 '상태'가 들어가 있고 그것을 다른 클라이언트가 접근해서 상태값을 바꿀 수 있다면 큰 문제를 초래할 수 있다.
package hello.core.singleton;
public class StatefulService {
private int price; //상태를 유지하는 필드
public void order(String name, int price) {
System.out.println("name = " + name + " price = " + price);
this.price = price; //여기가 문제임!!!
}
public int getPrice() {
return price;
}
}
위의 StatefulService가 스프링 컨테이너에 스프링 빈으로 등록 되어 사용된다면, 다음과 같은 문제점을 가질 수 있다.
class StatefulServiceTest {
void statefulServiceSingleton() {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//ThreadA: A사용자 10000원 주문
statefulService1.order("userA", 10000);
//ThreadB: B사용자 20000원 주문
statefulService2.order("userB", 20000);
//ThreadA: 사용자A 주문 금액 조회
int price = statefulService1.getPrice();
System.out.println("price = " + price);
}
static class TestConfig {
@Bean
public StatefulService statefulService() {
return new StatefulService();
}
}
}
위 코드에서 최종적인 price의 값은 당연히 20000이 되고 사용자A의 주문 금액은 실제로는 10000이었음에도 20000으로 출력된다. 실무에서도 더 복잡한 경우에서 드물지만 일어나는 에러라고 한다. 만약 금액과 관련된 부분에서 문제가 터진다면 큰 사고가 될 수도 있다.
따라서, 스프링 빈(싱글톤 패턴을 이용한)을 사용할 때는 다음과 같이 '무상태'로 설계하는 것이 중요하다.
package hello.core.singleton;
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;
// }
}
class StatefulServiceTest {
void statefulServiceSingleton() {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//ThreadA: A사용자 10000원 주문
int userAPrice = statefulService1.order("userA", 10000);
//ThreadB: B사용자 20000원 주문
int userBPrice = statefulService2.order("userB", 20000);
//ThreadA: 사용자A 주문 금액 조회
// int price = statefulService1.getPrice();
System.out.println("price = " + userAPrice);
}
static class TestConfig {
@Bean
public StatefulService statefulService() {
return new StatefulService();
}
}
}
참고: 김영한 스프링 핵심 기본 원리(인프런)
'Spring' 카테고리의 다른 글
| 롬북(Lombok)의 @RequiredArgsConstructor 기능 (0) | 2023.08.16 |
|---|---|
| 컴포넌트 스캔 (0) | 2023.08.15 |
| @Configuration의 역할과 싱글톤 (0) | 2023.08.14 |
| 좋은 객체 지향 설계 5가지 원칙(SOLID) (0) | 2023.08.09 |
| 스프링과 스프링부트 (0) | 2023.08.09 |