-
Notifications
You must be signed in to change notification settings - Fork 0
스프링 핵심편 Section5
- spring은 기업용으로 탄생
- 대부분 spring app → web app
- demon, batch도 개발이 가능
- web app은 보통 여러 고객이 동시 요청
AppConfig는 요청을 할 때마다 객체를 새로 생성
트래픽이 높아지면 높아질수록 더 많은 객체가 생성,소멸됨 → 메모리 낭비 심함
싱글톤 → 이 문제를 해결
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance(){
return instance;
}
private SingletonService(){
}
public void logic() {
System.out.println("싱글톤 객체 로직 호출");
}
- 딱 1개의 객체 인스턴스만 존재해야 하므로, 생성자를 private으로 막아서 혹시라도 외부에서 new 키워드로 객체 인스턴스가 생성되는 것을 막는다.
- 컴파일 에러로만 많은 에러를 잡을 수 있는 것이 잘 설계한 객체
assertion에서 same, equal
same ⇒ java = ⇒ 진짜 인스턴스가 같은지
equal ⇒ java equals
AppConfig를 모두 싱글톤으로 바꿔야 하나요?
→ spring container에서 알아서 싱글톤으로 해줍니다.
싱글톤 패턴의 문제점
- 구현 코드 자체가 많이 들어간다.
- 의존관계상 클라이언트가 구체 클래스에 의존 → DIP 위반
- getInstance를 불러와야 하기 때문
- client가 구체 클래스에 의존해서 OCP 위반 가능성이 크다.
- 유연하게 테스트 어렵다.
- singleton은 미리 instance를 박아 놓기에 설정이 끝나기 때문
- 내부 속성 변경, 초기화 어려움
- private 생성자로 자식 클래스 만들기 어려움
- 안티패턴으로 불리기도 한다.
Spring Container는 싱글톤 패턴의 문제점을 해결, 싱글톤으로 관리.
Spring Bean → Singleton으로 관리되는 Bean
싱글톤 컨테이너
- Spring Container는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리
- 컨테이너는 객체를 하나만 생성해서 관리
- Spring Container→ Singleton Container, Singleton 객체를 생성 관리하는 기능 → Singleton Registry
- 이러한 기능 덕에 Singleton 패턴의 모든 단점을 해결, 객체를 Singleton으로 유지
- 지저분한 코드 불필요
- DIP, OCP, 테스트, private 생성자로부터 자유로움
싱글톤 컨테이너 적용 후
- client의 요청이 들어올 때 마다 객체를 생성하는 것이 아닌, 이미 만들어진 객체를 공유해서 재사용
스프링 기본 빈 등록 방식은 singleton, but 새로운 객체를 생성해서 반환하는 기능도 제공 → 빈 스코프
singleton방식은 여러 client가 하나의 같은 객체 인스턴스를 공유하기 때문에, 싱글톤 객체의 상태는 stateless하게 설계해야 한다.
- 특정 client에 의존적인 필드가 있으면 안된다.
- 특정 client가 값을 변경할 수 있는 필드가 있으면 안된다.
- 가급적 읽기만 가능해야 한다.
- 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
Spring bean의 필드에 공유 값을 설정하면 큰 장애가 발생
void statefulServiceSingleton(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//ThreadA: A사용자가 10000원 주문
statefulService1.order("userA", 10000);
//ThreadA: B사용자가 20000원 주문
statefulService2.order("userB", 20000);
//ThreadA: 사용자 A 주문 금액 조회
int price = statefulService1.getPrice();
System.out.println("price = " + price);
Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
statefulService는 같은 객체이기에 10000원에서 20000원으로 바뀌게 됨
private int price; // 상태를 유지하는 필드
public void order(String name, int price){
System.out.println("name = " + name + ", price = " + price);
this.price = price; // 여기가 문제
}
이렇게 설정되어 있는 것을
//private int price; // 상태를 유지하는 필드
public int order(String name, int price){
System.out.println("name = " + name + ", price = " + price);
//this.price = price; // 여기가 문제
return price;
}
이렇게 변경하자
실무에서 이러한 일을 꼭 보게 된다.. thread문제
Configuration은 싱글톤을 위해 존재하는 것이다.
//@Bean memberService -> new MemoryMemberRepository()
//@Bean orderService -> new MemoryMemberRepository()
- 객체를 분명 2개를 생성하는 것 처럼 보인다. 에러 아닌가?
TroubleShooting
ConfigurationTest를 진행했는데 모두 다른 것이 떠서 왜인가 확인했더니
AppConfig에 @Bean에 static이 붙어있었기 때문!
지우니 테스트 성공