스프링은 태생이 기업용 온라인 서비스 기술을 지원하기 위해 탄생했다.
대부분의 스프링 애플리케이션은 웹 애플리케이션이다. 물론 웹이 아닌 애플리케이션 개발도 얼마든지 개발할 수 있다.
웹 애플리케이션은 보통 여러 고객이 동시에 요청을 한다.
이렇게 고객3명이 요청을 하면 객체 3개를 생성해야 한다.
고객 트래픽이 초당 100이 나오면 초당 100개 객체가 생성되고 소멸된다. -> 메모리 낭비가 심하다.
=> 해당 객체가 하나만 생성되도록 하고 공유하도록 설계하면 된다.
싱글톤 패턴
객체 인스턴스를 2개 이상 생성하지 못하도록 막아야 한다.
- private 생성자를 이용하여 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야 한다.
1
2
3
4
5
6
7
8
9
10
public class SingletonService{
private static final SingletonService instance = new SingletonService();
private SingletonService(){
} //생성자를 private으로 해놓음으로써 외부의 new를 방지.
public static SingletonService getInstance(){
return instance;
}
}
문제점
- 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
- 의존관계상 클라이언트가 구체 클래스에 의존한다. -> DIP를 위반
- 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
- 테스트하기 어렵다.
- 내부 속성을 변경하거나 초기화 하기 어렵다.
- private 생성자로 자식 클래스를 만들기 어렵다.
- 결론적으로 유연성이 떨어진다.
- 안티패턴으로 불리기도 한다.
싱글톤 컨테이너
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다. 스프링 빈이 바로 싱글톤으로 관리되는 빈이다.
- 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다.
- DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있다.
스프링 컨테이너 덕분에 고객의 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 재사용할 수 있다.
** 기본 빈 등록 방식은 싱글톤이지만 요청할 때 마다 새로운 객체를 생성해 반환하는 방식도 지원한다. (99%는 싱글톤만 사용함.)
☆싱글톤 방식의 주의점
싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 상태를 유지하도록 설계하면 안된다.
- 무상태(Stateless)로 설계해야 한다.
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
- 가급적 읽기만 가능해야 한다.
- 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
상태를 유지할 경우 발생하는 문제점 예시
1
2
3
4
5
6
7
8
9
10
11
12
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;
}
}
사용자1이 만원 주문을 넣은 상태에서 사용자 2가 2만원 주문을 넣으면 price가 2만원으로 바뀌는 문제가 발생함. 사용자1이 조회하면 만원이 나와야 하는데 2만원이 나오는 상황이 발생.
- 무상태
1
2
3
4
5
6
public class StateFulService {
public int order(String name, int price){
System.out.println("name= " + name + " price= " + price);
return price;
}
}
** 진짜 공유필드는 조심해야한다! 스프링 빈은 항상 무상태로 설계하자.
@Configuration과 싱글톤
1
2
3
4
5
6
7
8
9
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
이 상황에서 memberRepository() 두 번 불려서 new MemoryMemberRepository() 두번 불리는데 싱글톤 깨지는거 아니야? 라는 의심이 듦.
스프링 컨테이너는 이 문제를 어떻게 해결할까?
테스트를 해보면 동일한 인스턴스를 공유한다.
혹시 두 번 호출을 하지 않는 것이 아닐까?
-> 호출을 실제로 한번씩밖에 안함.
@Configuration과 바이트코드 조작의 마법
호출을 한번씩밖에 안하는 비밀 -> @Configuration
스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용하여 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것.
그 임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다.
- AppConfig@CGLIB 간단 예상 코드
1
2
3
4
5
6
7
8
9
10
@Bean
public MemberRepository memberRepository(){
if(memoryMemberRepository가 이미 스프링 컨테이너에 등록되어있으면?){
return 스프링 컨테이너에서 찾아 반환;
}
else{
기존 로직을 호출해서 MemoryRepo 생성하고 스프링컨테이너에 등록
return 반환
}
}
@Configuration을 적용하지 않고, @Bean만 적용하면?
AppConfig가 CGLIB 적용되지 않고 순수하게 빈으로 등록되는 것을 알 수 있다.
-> 싱글톤이 깨진다.
즉 @Bean만 사용해도 스프링 빈에 등록은 되지만 @Configuration을 적용하지 않으면 싱글톤을 보장해주지 않는다.
memberRepoistory() 처럼 의존관계 주입이 필요해서 메서드를 직접 호출할 때 싱글톤을 보장해주지 않으므로
크게 고민할 것 없이 스프링 설정 정보는 항상 @Configuration을 이용하자.