@Configuration과 싱글톤
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
}
}
- memberService를 호출하게 되면 memberRepository가 호출되고 MemoryMemberRepository 가 호출 된다.
- orderService를 호출하게 되면 memberRepository가 호출 되고 MemoryMemberRepository 가 호출 된다.
분명 싱글톤 인데 호출이 여러 개 된다. 이것은 싱글톤이 깨지는 걸까?
@Test
public 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);
//모두 같은 인스턴스를 참고하고 있다.
System.out.println("memberService -> memberRepository = " + memberService.getMemberRepository());
System.out.println("orderService -> memberRepository = " + orderService.getMemberRepository());
System.out.println("memberRepository = " + memberRepository);
//모두 같은 인스턴스를 참고하고 있다.
assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
}
Test 해보기 위해 각각 만들어서 출력해보 앗다.
신기하게도 모두 똑같이 출력되었다.
//AppConfig 파일
@Bean
public MemberService memberService(){
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
AppConfig 파일을 이렇게 변경해서 출력되는 과정을 보기로 했다.
- 스프링 컨테이너가 스프링 빈에 등록하기 위해 @Bean이 붙어있는 memberRepository() 호출
- MemberService에서 MemberRepository를 호출했으므로 "call AppConfig.memberRepository" 출력
- orderService() 로직에서 memberRepository() 호출
총 "call AppConfig.memberRepository"를 3번 출력할 거 같다.
- MemberService를 호출 했으므로 "call AppConfig.memberService" 출력
- memberRepository() 호출 했으므로 "call AppConfig.memberRepository" 출력
- 등록해야 하므로 "call AppConfig.memberRepository" 출력
- orderService를 호출해야 하므로 "call AppConfig.orderService" 출력
- 마지막으로 "call AppConfig.memberRepository"
이렇게 출력될 것 같다.
하지만 신기하게도 한 번씩만 출력된다!!! 왜 그런 것일까???
@Configuration과 바이트코드 조작의 마법
스프링 컨테이너는 싱글톤 레지스트리다. 따라서 스프링 빈이 싱글톤이 되도록 보장해주어야 한다. 그런데 스프링이 자바 코드까지 어떻게 하기는 어렵다. 저 자바 코드를 보면 분명 3번 호출되어야 하는 것이 맞다. 그래서 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용한다. 모든 비밀은 @Configuration을 적용한 AppConfig에 있다.
@Test
void configurationDeep(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//AppConfig도 스프링 빈으로 등록된다.
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
}
AnnotationConfigApplicationContext에 파라미터로 넘긴 값은 스프링 빈으로 등록된다. 그래서 AppConfig 도 스프링 빈이 된다.
그래서 AppConfig 스프링 빈을 조회해서 클래스 정보를 출력해 봤다.
신기하게도 AppConfig 뒤에 다른 문자들이 붙어있다.
그런데 예상과는 다르게 클래스 명에 xxxCGLIB가 붙으면서 상당히 복잡해진 것을 볼 수 있다.
이것은 내가 만든 클래스가 아니라 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것이다.
즉, AppConfig가 AppConfig@CGLB라는 것을 하나 만들어서(상속받아서) 스프링 컨테이너에 집어넣는다. 이름은 appConfig이지만 istance는 AppConfig@CGLIB이다!!
이 임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다.
- AppConfig@CGLIB 예상 코드
@Bean
public MemberRepository memberRepository() {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
return 스프링 컨테이너에서 찾아서 반환;
} else { //스프링 컨테이너에 없으면
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 반환
}
}
이 코드는 강사님이 설명해 주실 때 사용하신 AppConfig@CGLIB 예상 코드이다.
만약 memoryMemberRepository가 스프링 컨테이너에 존재하지 않으면 바로 스프링 컨테이너에 등록한다. 하지만 존재하면 스프링 컨테이너에서 찾아서 반환한다. 이렇게 하기 때문에 각각 제일 처음 스프링 컨테이너에 등록할 때 만 호출된 것이다. 덕분에 싱글톤이 보장되는 것이다.
AppConfig@CGLIB는 AppConfig의 자식 타입이므로, AppConfig 타입으로 조회할 수 있다.
@Configuration 을 적용하지 않고, @Bean 만 적용하면 어떻게 될까?
@Configuration 을 붙이면 바이트코드를 조작하는 CGLIB 기술을 사용해서 싱글톤을 보장을 한다.
하지만 Bean만 실행하면 bean = class hello.core.AppConfig이 출력 된다.
이 5가지가 모두출력 되게 된다. 이 출력 결과를 통해서 MemberRepository가 총 3번 호출된 것을 알 수 있다. 1번은 @Bean에 의해 스프링 컨테이너에 등록하기 위해서이고, 2번은 각각 memberRepository() 를 호출하면서 발생한 코드다.
싱글톤이 깨지게 된다!!
'Spring 이론 공부 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
의존관계 자동 주입(1) (0) | 2023.02.19 |
---|---|
컴포넌트 스캔 (0) | 2023.02.07 |
싱글톤 컨테이너 (0) | 2023.02.03 |
스프링 컨테이너와 스프링 빈(추가) (0) | 2023.02.03 |
스프링 컨테이너 생성 및 생성 과정 (0) | 2023.01.30 |