@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(getMemberRepository());
}
@Bean
public static MemoryMemberRepository getMemberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(getMemberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
// return new FixDiscountPolicy();
}
}
우리가 앞서 작성했던 AppConfig 코드를 봐보자. 우선
@Bean memvberService가 -> new MemberyMemberRepository() 를 호출하고
@Bean orderService가 -> new MemberyMemberRepository() 가 또 호출한다.
그럼 2번 new 생성자 함수가 호출되었으니 싱글톤이 깨지는 것이 아닌가? 하고 생각할 수 있다.
그렇다면 스프링 컨테이너는 이를 어떻게 해결할까? => 바이트코드 조작의 마법
우선 그전에 문제를 하나 봐보자.
public class AppConfig {
@Bean
public MemberService memberService() {
System.out.println("call MemberService");
return new MemberServiceImpl(getMemberRepository());
}
@Bean
public static MemoryMemberRepository getMemberRepository() {
System.out.println("call MemoryMemberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
System.out.println("call OrderService");
return new OrderServiceImpl(getMemberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
// return new FixDiscountPolicy();
}
}
스프링 컨테이너는 싱글톤 레지스트리다.
따라서 스프링 빈이 싱글톤이 되도록 보장해주어야한다.
그런데 스프링이 자바 코드까지 어떻게 하기는 어렵다.
즉, 위 문제에서 getMemberRepository 이 메서드는 3번 실행이 되었어야 했다는 것이다.
그런데 스프링은 클래스의 바이트ㅗ드를 조작하는 라이브러리를 사용한다.
그것은 @Configuration
을 적용한 AppConfig
에 있다.
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Bean
으로 등록이 된다.@Test
void configurationDeep(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
위 AppConfig를 위와같은 테스트 코드에 넣고 돌리면 이러한 결과가 나온다. 자세히 보면 core.AppCinfig뒤에 이상한게 막 붙어있는 것을 확인할 수 있다.
원래 순수 클래스라면 class hello.core.AppConfig
라고 나와야 정상이다. 그런데 이상하게 씨쥐리브(CGLIB)가 붙으면서 이상한 클래스가 튀어나온 것을 확인할 수 있는데,
이것은 스프링이 CGLIB
이라는 "바이트 코드 조작 라이브러리" 를 사용해서
AppConfig 클래스를 상속받는 임의의 다른 클래스를 만들고,
그 다른 클래스를 스프링 빈으로 등록한 것이다.
그래서 사실 내가 작성한 진짜 AppConfig는 사라지고 스프링이 상속한 AppConfig@CGLIB
이 등록되는 것이다.
그래서 이 AppConfig@CGLIB
이 싱글톤이 되도록 보장해주는 것이다.
그럼 또 똑똑한 학생은
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
아니 그럼 여기서 내가 작성한 자바 코드인 본래의 AppConfig가 나와야하는거 아니냐~ 할 수 있는데
위 사진에서 볼 수 있듯 AppConfig를 상속받은 AppConfigCGLIB이 AppConfig이름도 갖고가서 등록해버려서 AppConfig를 조회해도 앞서 언급했던 것 처럼 부모를 조회하면 자식이 다 딸려나와서 AppConfigCGLIB이 나오는 것인데 우리가 작성한 AppConfig가 나오는 것 처럼 보이는 것이다.
=> 물론 안 해도 되긴 하다. 그러나 문제가 있다. 무슨 문제? 싱글톤이 깨진다.
바이트코드를 조작하지 않아서 우리가 작성한 진짜 순수의 클래스가 나온다.
즉, 스프링컨테이너에서 관리하는 객체가 아니게 된것이다.
@Bean만 사용해도 스프링 빈으로 등록이 되긴하는데 싱글톤이 보장되지 않는다.
특히 AppConfig에서 의존관계 주입이 필요해서 메서드를 직접 호출할 때 싱글톤을 보장하지 않는다.