1. 스프링과 비교
- 웹 어플리케이션은 보통 여러 고객이 동시에 요청
- 스프링없는 순수한 DI 컨테이너는 AppConfig 요청을 할떄마다 객체 생성.
- 객체 인스턴스를 생성하는 비용이 1000 이라면 가져오는 비용은 1정도로 차이가 큼.
2. 싱글톤 패턴의 문제점
- 싱글톤 패턴 구현 코드 많이 들어감.
- 의존 관계상 클라이언트가 구체 클래스에 의존(DIP 위반)
- 클라이언트가 구체 클래스에 의존해서 OCP위반 가능성 높음.
- 테스트 하기 어려움.
- 내부 속성을 변경ㅎ거나 초기화 하기 어렵다.
- private생성자로 자식 클래스를 만들기 어렵다.
- 결론적으로 유연성 떨어진다.
- 안티패턴으로 불리기도 한다.
3. 싱글톤 컨테이너
- 스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다.
- 스프링 컨테이너는 컨테이너 객체를 하나만 생성해서 관리.
- ==> 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다.
- 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라고 한다.
- 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 되고 DIP OCP, 테스트, private생성자로 부터 자유롭게 싱글톤 사용 가능.
4. 싱글톤 방식의 주의점
- 싱글톤 패턴이든 싱글톤 컨테이너이든 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지하기(stateful)하게 설계하면 안된다
- stateless로 설계 해야 된다.
- 특정 클라이언트에 의존적인 필드가 있으면 안된다.
- 특정 클라이언트가 값을 변경할 수 있으면 안된다.
- 가급적 읽기만 가능해야 한다.
- 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, TreadLocal등을 사용해야 한다.
- ***스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애 발생 가능!!
public class StatefulService{
private int price;
public void order(String name, int price){
System.out.rptintln("name = " + name + "price = " + price);
this.price = price; //문제 부분
}
}
//변경!
public class StateLessService{
//private int price;
public int order(String name, int price){
System.out.rptintln("name = " + name + "price = " + price);
//this.price = price; //문제 부분
return price;
}
}
- price필드는 공유되는 필드 인데, 특정 클라이언트가 값을 변경. (공유 필드 조심!!! 스프링 빈은 항상 무상태로 설계)
5. AppConfig관련 이슈
- @Confiugration없이 @Bean만 있으면 Bean등록 되지만 싱글톤 보장하지는 않음. (바이트 코드 조작 없이 각각 호출)
- 또한 스프링 컨테이너에서 관리도 되지 않는다. (그저 의존성 주입만 해준 걸까..?)
- AppConfig가 사용하는 인스턴스에 @Autowired를 붙이면 스프링 컨테이너에서 끌어 오지만 권장하는 사용법은 아님.
- 같은 객체를 AppConfig에서 여러번 호출하는 경우가 생기는데 이때 싱글톤이 깨지는게 아닐까?
- 바이트 코드를 조작하여 AppConfig를 상속받은 임의의 다른 클래스를 만들어 스프링 빈에 등록 (@CGLIB)
- 이 글래스가 싱글톤 보장
//예상
@Bean
public MemberRepository memberRepository(){
if(memoryMemberRepository가 이미 스프링 컨테이너에 등록됨?){
return 스프링 컨테이너에서 찾아서 반환;
}else{
기존 로직을 호출해서 MemoryMemberRepository생성하고 스프링 컨테이너에 등록
return 반환
}
}
- 조회 코드
- AppConfig@CGLIB는 AppConfig의 자식 클래스라서 조회 됨
@Test
void configurationDeep(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}