Spring 시리즈는 혼자 공부하며 기록으로 남기고, 만약 잘못 학습 한 지식이 있다면 공유하며 피드백을 받고자 작성합니다.
스프링에 대해 깊게 공부해보고자 인프런의 김영한 강사님께서 강의를 진행하시는 (스프링 핵심 원리 - 기본편) 강의를 수강하며 정리하는 글입니다.
혹여나 글을 읽으시며 잘못 설명된 부분이 있다면 지적 부탁드리겠습니다.
AppConfig의 코드를 보면 뭔가 이상하지 않은가?@Configuration
public class AppConfig {
//@Bean memberService -> new MemoryMemberRepository()
//@Bean orderService -> new MemoryMemberRepository(), new RateDiscountPolicy()
//MemoryMemberRepository 두번 호출되는데... 싱글톤이 깨지는건 아닌가?
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
MemoryMemberRepository가 총 3번 생성된다.public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
//테스트 용도
public MemberRepository getMemberRepository() {
return memberRepository;
}
}
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
//테스트 용도
public MemberRepository getMemberRepository() {
return memberRepository;
}
}
public class ConfigurationSingletonTest {
@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("memberRepository1 = " + memberRepository1);
System.out.println("memberRepository2 = " + memberRepository2);
System.out.println("memberRepository = " + memberRepository);
assertThat(memberRepository1).isSameAs(memberRepository);
assertThat(memberRepository2).isSameAs(memberRepository);
}
}
결과

@Configuration 애노테이션을 적용한 AppConfig에 있다.클래스명 확인 테스트 코드
public class ConfigurationSingletonTest {
...
@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
}
출력
bean = class hello.core.AppConfig$$SpringCGLIB$$0
AnnotationConfigApplicationContext의 파라미터로 넘긴 값도 스프링 빈으로 등록된다.CGLIB라는 바이트코드 조작 라이브러리를 사용하여 AppConfig를 상속받은 임의의 다른 클래스를 만들어 스프링 빈으로 등록한다.그림

AppConfig@CGLIB 예상 코드
@Bean
public MemberRepository memberRepository() {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
return 스프링 컨테이너에서 찾아서 반환;
} else { //스프링 컨테이너에 없으면
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 반환
}
}
@Bean 이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환한다.참고 AppConfig@CGLIB는 AppConfig의 자식타입으로, AppConfig 타입으로 조회가 가능하다.
public class ConfigurationSingletonTest {
...
@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
}
결과
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.orderService
call AppConfig.memberRepository
call AppConfig.memberRepository
bean = class hello.core.AppConfig
memberRepository가 3번 불러지는 걸 볼 수 있다.또 다른 테스트 코드
public class ConfigurationSingletonTest {
@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("memberRepository1 = " + memberRepository1);
System.out.println("memberRepository2 = " + memberRepository2);
System.out.println("memberRepository = " + memberRepository);
assertThat(memberRepository1).isSameAs(memberRepository);
assertThat(memberRepository2).isSameAs(memberRepository);
}
}
결과
memberRepository1 = hello.core.member.MemoryMemberRepository@35229f85
memberRepository2 = hello.core.member.MemoryMemberRepository@6d3c5255
memberRepository = hello.core.member.MemoryMemberRepository@b1712f3