월루를 꿈꾸는 대학생
[핵심원리-기본] 싱글톤 컨테이너 본문
웹 애플리케이션과 싱글톤
스프링은 기업용 서비스 기술 목적이며 이런 경우 보통 여러 고객이 동시에 요청을한다
AppConfig는 new해서 새로 만들어서 반환하지 그래서 클라이언트가 요청할 때마다 생성해서 반환해서 주는 거임.. 무수한 인스턴스가 생성이 된다!
메모리 낭비가 심하다
호출마다 서로 다른 객체가 생성되므로 효율적이지 않음
그래서 딱 1개의 객체만 생성되고 공유하도록 설계를 해야한다 =>싱글톤 패턴
싱글톤 패턴
인스턴스 1개만 생성하고 쓰는 것
2개 이상 못 만들도록 막아야함
-> private 생성자를 통해서 외부에서 맘대로 new 키워드 못 사용하도록 막아야함!!!
public class SingletonService {
// 자기 자신을 private로 선언한 후에 new로 인스턴스 생성
// static : 클래스 레벨로 올라가니까 1개만 올라감 -> 자바 실행시에 static 친구들은 내부적으로 메모리 올라감 (즉 미리 만들어짐)
private static final SingletonService instance = new SingletonService();
//만들어진 인스턴스 반환해서 사용할 수 있도록
public static SingletonService getInstance(){
return instance;
}
// private로 선언해서 다른 코드에서 new 못쓰도록 막음
private SingletonService(){
}
public void logic(){
System.out.println("싱글톤 객체 로직 호출 ");
}
}
- static으로 변수 선언해서 자바 실행시 싱글톤 인스턴스 1개가 만들어져서 내부 메모리에 올라가도록
- getInstance로만 참조할 수 있도록하고 생성자에 private붙여서 외부에서 생성 막음
- 같은 인스턴스가 나옴을 확인
이렇게 싱글톤 적용을 위해선 생성자부터 다시 싹 만들어야 하는데 스프링을 사용하면 그냥 스프링 임마가 알아서 싱글톤으로 만들어준다고 함 ;;
싱글톤 패턴 문제점
- 구현 코드에 공수가 더 들어간다
- 의존관계상 클라이언트가 구체 클래스 의존 -> DIP위반 getInstance이렇게 써야함;;
- 클라이언트가 구체 클래스에 의존 OCP의존
- 테스트 어렵
- private 생성자 쓰니까 자식 클래스 만들기 어렵다
싱글톤 컨테이너
스프링 컨테이너
- 자동으로 객체 인스턴스를 싱글톤으로 관리
- 스프링 컨테이너 = 싱글톤 컨테이너
- 싱글톤 객체 생성 관리 기능 = 싱글톤 레지스트리
- 스프링은 자동으로 이런 관리를 해주니까 싱글톤 패턴의 단점을 해결하면서 객체를 싱글톤 유지 가능
- 싱글톤 패턴을 위해서 코드 공수 추가 x
- DIP,OCP,private 등등 자유롭게 싱글톤 사용
@Test
@DisplayName("스프링 컨테이너와 테스트")
void springContainer(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// 스프링 컨테이너가 알아서 싱글톤으로 만들어서 반환해줌 즉 memberService1,memberService2는 같은 객체 참조
MemberService memberService1 = ac.getBean("memberService",MemberService.class);
MemberService memberService2 = ac.getBean("memberService",MemberService.class);
System.out.println("memberservice1 : "+ memberService1);
System.out.println("memberservice2 : "+ memberService2);
assertThat(memberService1).isSameAs(memberService2);
}
고객이 요청할 때마다 객체 생성이 아니라 이미 만들어준 객체를 공유해서 효율적인 사용 가능
보통은 싱글톤으로 사용한다
싱글톤 방식의 주의점
- 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하니까 절대로 상태를 유지하게 설계하면 안 된다 -> stateless 설계가 필요
- 무상태 설계가 필요
- 특정 클라이언트에 의존적 필드 x -> 수정 x , 읽기만
- 필드 대신에 자바에서 공유되지 않는 지역변수 , 파라미터 , ThreadLocal 등 사용해야함
- 스프링 빈에 공유 값 설정하면 망함;
public class StatefullService {
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;
}
}
- 여기서 보면 this.price로 변수를 가지고 있음 이 변수는 싱글톤 사용할 때 망하는 지름길 여러사람이 값을 바꿀 수 있으니까
테스트
@Test
void statefulServiceSingleton(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefullService statefullService1 = ac.getBean(StatefullService.class);
StatefullService statefullService2 = ac.getBean(StatefullService.class);
//ThreadA: 사용자A 10000원 주문
statefullService1.order("userA",10000);
//ThreadB: 사용자B 20000주문
statefullService2.order("userB",20000);
//ThreadA: 사용자A 주문 금액 조회
int price = statefullService1.getPrice();
System.out.println("price = "+ price);
Assertions.assertThat(statefullService1.getPrice()).isEqualTo(20000);
}
이런 경우 출력으로 20000이 나옴
왜냐하면 이미 statefullService2가 같은 인스턴스를 참조해서 값을 바꿨기 때문에
이런 문제가 생길 수 있으니까 무상태 stateless 설계를 해야한다
이런 식으로 무상태로 상태를 저장하지 않도록 설계 하는 것이 중요
@Configuration 과 싱글톤
참고
https://www.inflearn.com/questions/609357/comment/202743
appconfig에 static으로 되어 있으면 싱글톤 적용 안됨!!
@Configuration // 어노테이션 추가 구성정보를 담당한다는 뜻
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
private static MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
// @Bean memberService -> new MemoryMemberRepository()
// @Bean orderService -> new MemoryMemberRepository()
보면 첨 구성파일을 스프링 컨테이너에 올릴 때 MemoryMemberRepository를 두 번 생성하게 될텐데 이러면 싱글톤이 깨지는 것이 아닌가??
@Test
void configurationTest(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
MemberRepository memberRepository = ac.getBean("memberRepository",MemberRepository.class);
MemberRepository memberRepository1 = memberService.getMemberRepository();
MemberRepository memberRepository2 = orderService.getMemberRepository();
System.out.println("memberService : " + memberRepository1);
System.out.println("orderService : " + memberRepository2);
System.out.println("memberRepository : " + memberRepository);
Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
}
- 확인해보면 모두 같은 인스턴스를 공유해서 쓰는 싱글톤으로 되어 있음을 확인 가능
- AppConfig에서는 분명 2번씩 호출이 될텐데 어떻게 하나로 공유를 하는 것인가 .... 밑에 또 테스트 해봐야함
실제 테스트 결과
call memberRepository
call orderService
call discountPolicy
보면 딱 한 번만 실행해서 싱글톤을 보장해줌 !!!
@Configuration과 바이트코드 조작의 마법
스프링 컨테이너 = 싱글톤 레지스터 = 싱글톤을 보장
원래라면 분명 memberRepository가 3번 호출이 되어야하는데 1번만 호출 됨 그 비밀은 @Configuration임
@Test
void configurationDeep(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// AppConfig도 스프링 빈으로 등록이 됨
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean : "+bean.getClass());
// 출력결과 : bena : class hello.core.AppConfig$$EnhancerBySpringCGLIB$$4a5fc277}
보면 출력결과가 AppConfig에서 끝나는게 아니라 머 이상한거 더 붙어 있음
hello.core.AppConfig==$ $ EnhancerBySpringCGLIB$ $4a5fc277==
즉 내가 만든 클래스가 올라간게 아님.. 내가 만든 클래스 이름은 AppConfig니까
무슨 CGLIB 가 붙어 있음 저건 스프링이 CGLIB 바이트 코드 조작 라이브러리를 사용해서 내가 만들어둔 AppConfig를 상속한 새로운 클래스를 만들어서 그 클래스를 빈에 등록 시킨거다!!
즉 내가 만든게 아니라 한 번 수정을 거친 클래스가 올라갔고 이 수정을 한 클래스에서 내부적으로 싱글톤을 유지하는 코드를 추가해서 싱글톤을 유지 시킨 것
- @Bean 메서드마다 스프링 빈에 이미 존재하는지 유무 체크해서 없으면 생성후 빈 등록 있으면 등록된 인스턴스 생성해서 반환 이런식으로 싱글톤 보장
@Configuration 적용 안 하고 @Bean만 적용한다면?
-> @Configuration 을 써야 바이트 코드 조작 라이브러리를 써서 싱글톤을 보장해줬는데 이거 없으면 싱글톤 깨짐
@Bean만 써도 스프링 빈 등록해서 쓸 수 있지만 싱글톤은 보장하지 않는다
웬만하면 그냥 설정 정보에는 @Configuration 사용하자
'Programing > Spring Boot' 카테고리의 다른 글
[핵심원리 - 기본] 의존관계 자동 주입 (0) | 2023.07.18 |
---|---|
[핵심원리-기본] 컴포넌트 스캔 (0) | 2023.07.18 |
[핵심원리-기본] 스프링 컨테이너와 스프링빈 (0) | 2023.07.18 |
[핵심원리-기본] 객체지향 원리 적용 (0) | 2023.07.18 |
[핵심 원리-기본]비즈니스 요구사항과 설계 (0) | 2023.07.18 |