@Configuration
public class AppConfig {
//@Bean memberService -> new MemoryMemberRepository()
//@Bean orderService -> new MemoryMemberRepository()
@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 RateDiscountPolicy();
}
}
- memberService 빈을 만드는 코드를 보면 memberRepository()를 호출한다.
- 이 메소드를 호출하면 new MemoryMemberRepository()를 호출한다.
- orderService 빈을 만드는 코드도 동일하게 memberRepository를 호출한다.
- 이 메소드를 호출하면 new MemoryMemberRepository()를 호출한다.
- 결과적으로 각각 다른 2개의 MemoryMemberRepository가 생성되면서 싱글톤이 깨지는 것 처럼 보인다.
@Test
void configurationTest() {
//given
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
final MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
final OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
final MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
//when
final MemberRepository memberRepository1 = memberService.getMemberRepository();
final MemberRepository memberRepository2 = orderService.getMemberRepository();
//then
assertThat(memberRepository1).isSameAs(memberRepository2);
assertThat(memberRepository).isSameAs(memberRepository1);
assertThat(memberRepository).isSameAs(memberRepository2);
}
- 확인해보면 memberRepository 인스턴스는 모두 같은 인스턴스가 공유되어 사용된다.
- AppConfig의 자바 코드를 보면 분명히 각가 2번 new MemoryMemberRepository가 호출되서 다른 인스턴스가 생겨야 하는데 말이다.
- 스프링 컨테이너는 싱글톤 레지스트리다.
따라서 스프링 빈이 싱글톤이 되도록 보장해주어야 한다.
그런데 스프링이 자바 코드까지 어떻게하기는 어렵다.
- 따라서 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용한다.
@Test
void configurationDeep() {
//given && when
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
final AppConfig bean = ac.getBean(AppConfig.class);
//then
System.out.println("bean.getClass() = " + bean.getClass());
}
bean.getClass() = class hyun6ik.corerepeat.config.AppConfig$$
EnhancerBySpringCGLIB$$ef138311
- 예상 했던 결과 : class hyun6ik.corerepeat.config.AppConfig
- 그런데 예상과는 다르게 xxxCGLIB가 붙으면서 상당히 복잡해진 것을 볼 수 있다.
이것은 스프링이 CGLIB 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고 그 다른 클래스를 스프링 빈으로 등록한 것이다. (프록시)
- 그리고 그 임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다.
@Bean
public MemberRepository memberRepository() {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면 ) {
return 스프링 컨테이너에서 찾아서 반환;
}
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록 한 후
return 반환
}
- @Bean이 붙은 메소드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고 스프링 빈이 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어진다.
- 덕분에 싱글톤이 보장된다.
cf) AppConfig@CGLIB는 AppConfig의 자식 타입 이므로 AppConfig 타입으로 조회할 수 있다.
- 순수한 AppConfig로 스프링 빈에 등록이 된다.
- 하지만 싱글톤을 보장하지 못한다.
- 스프링 컨테이너에서 관리해주지 않는다.
- @Bean만 사용해도 스프링 빈으로 등록되지만 싱글톤을 보잫하지 않는다.
- memberRepository() 처럼 의존관계 주입이 필요해서 메소드를 직접 호출할 때 싱글톤을 보장하지 않는다.
- 때문에 스프링 설정 정보는 항상 @Configuration을 사용하자.