웹 어플리케이션과 싱글톤
대부분의 스프링 애플리케이션은 웹 애플리케이션이다.
그리고 웹 애플리케이션은 여러 고객이 동시에 요청을 한다.
요청할때마다 객체를 만들면 낭비가 심하다.
그러므로 싱글톤 패턴으로 설계하는게 효율적이다.
싱글톤 패턴
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
자바에서 배운것과 같이, private static으로 생성하고 getInstance를 통해서만 조회할 수 있도록 하면 된다.
이러면 단 하나의 인스턴스를 여럿이 돌려쓰게 된다.
하지만 이런 싱글톤 패턴은 문제점이 많다.
- 코드 자체가 많이 들어감.
- 클라이언트가 구체 클로스에 의존(DIP 위반)
- 초기화 힘듦
- private이라 자식 클래스 만들기 힘듬
싱글톤 컨테이너
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤(1개만 생성)으로 관리한다.
이것이 바로 스프링 기본 3에서 언급했던 스프링 컨테이너의 장점 중 하나이다.
지금까지 우리가 학습한 스프링 빈이 바로 싱글톤으로 관리되는 빈이다.
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//1. 조회: 호출할 때 마다 같은 객체를 반환
MemberService memberService1 = ac.getBean("memberService",MemberService.class);
//2. 조회: 호출할 때 마다 같은 객체를 반환
MemberService memberService2 = ac.getBean("memberService",MemberService.class);
이렇게 getBean으로 조회하면 싱글톤처럼 같은 객체를 반환한다.
이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라 한다.
싱글톤 방식의 주의점
싱글톤 객체는 여럿이 쓰는 것이므로 stateless 하게 설계해야 한다.
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
- 가급적 읽기만 가능해야 한다.
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 statefulService1 = ac.getBean("statefulService", StatefulService.class);
StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);
//ThreadA: A사용자 10000원 주문
statefulService1.order("userA", 10000);
//ThreadB: B사용자 20000원 주문
statefulService2.order("userB", 20000);
//ThreadA: 사용자A 주문 금액 조회
int price = statefulService1.getPrice();
//ThreadA: 사용자A는 10000원을 기대했지만, 기대와 다르게 20000원 출력
System.out.println("price = " + price);
이렇게 테스트하면, A 사용자한테 B 사용자의 주문 내역이 출력된다.
공유필드 price를 특정 클라가 값을 변경해서 발생한 문제이다.
이렇게 공유필드를 두지 말고 파라미터로 받은 값을 바로 리턴해서 넘기는게 더 현명하다.
스프링 빈은 항상 무상태(stateless)로 설계하자.
@Configuration과 싱글톤
이제 다시 AppConfig를 보자.
@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();
}
}
맴버서비스와 오더서비스가 둘 다 memberRepository를 호출한다.
그 말은 new MemoryMemberRepository가 두 번 호출되었다는 소리이다.
이러면 싱글톤이 깨지는 것처럼 보이는데?
하지만 결론적으로 memberRepository 인스턴스는 모두 같은 인스턴스가 공유되어 사용된다.
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(
memberRepository(),
discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
//스프링 컨테이너가 한 번, memberService()에서 한 번, orderService()에서 한 번 호출
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
출력문을 찍어보면...
memberService, orderService 모두 호출됐음에도 memberRepository는 한 번만 출력된 것을 볼 수 있다.
@Configuration과 바이트코드 조작의 마법
스프링 컨테이너는 싱글톤 레지스트리다. 따라서 스프링 빈이 싱글톤이 되도록 보장해야 한다.
자바코드가 위와 같은 상황에서 싱글톤을 보장하기 위해, 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용한다.
@Configuration
public class AppConfig {
....
}
비밀은 @Configuration이 붙은 AppConfig에 있다.
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
사실 AnnotationConfigApplicationContext 에 파라미터로 넘긴 값은 스프링 빈으로 등록된다.
그래서 AppConfig 도 스프링 빈이 된다.
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//AppConfig도 스프링 빈으로 등록된다.
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
AppConfig 빈을 조회해서 출력해보자.
AppConfig뒤에 이상한게 붙어서 출력된다.
순수한 클래스를 출력할 땐 저런게 붙지 않았다.
저것의 정체는 내가 만든 클래스가 아니다. 스프링이 바이트코드 조작 라이브러리인 CGLIB를 사용해 AppConfig 상속받은 다른 클래스를 만들고, 걔를 스프링 빈으로 등록한 것이다. AppConfig@CGLIB는 AppConfig의 자식 타입이므로, AppConfig 타입으로 조회 할 수 있다.
그리고 이렇게 만들어진 AppConfig@CGLIB가 싱글톤을 보장하는 로직을 가지고 있다.
@Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 스프링 빈이 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어진다.
이 덕분에 자바 코드에서 new 빈객체()가 여러번 호출되어도 스프링은 싱글톤을 보장할 수 있다.
@Configuration이 빠진다면?
스프링빈에 순수한 AppConfig가 등록되고, 각각 다 다른 MemoryMemberRepository가 만들어져 싱글톤이 깨지게 된다.
그러므로 스프링 설정 정보는 항상 @Configuration을 사용하자.
이 포스팅은 인프런 김영한님의 '스프링 핵심 원리 기본편'을 듣고 정리한 것입니다.
강의 링크: https://url.kr/udopsk
'개발 관련 공부 > 스프링 김영한 로드맵' 카테고리의 다른 글
[스프링 기본] 7. 의존관계 자동 주입 (0) | 2022.10.14 |
---|---|
[스프링 기본] 6. 컴포넌트 스캔 (0) | 2022.10.12 |
[스프링 기본] 4. 스프링 컨테이너와 스프링 빈 (0) | 2022.10.10 |
[스프링 기본] 3. 스프링 핵심 원리 이해2 -객체 지향 원리 작용 (0) | 2022.10.10 |
[스프링 기본] 2. 스프링 핵심 원리 이해1-예제 만들기 (0) | 2022.10.07 |
댓글