Configuration 애너테이션
인프런 스프링 핵심 원리 - 기본편 수강 중
Configuration 애너테이션과 싱글톤
AppConfig
@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 RateDiscountPolicy();
}
}
memberService
빈:memberRepository()
호출new MemoryMemberRepository()
호출
orderService
빈:memberRepository()
호출new MemoryMemberRepository()
호출
- 2개의
MemoryMemberRepository
가 생성되면 싱글톤이 깨질까?
테스트 코드
검증용으로 임시 코드 추가
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("memberService -> memberRepository = " + memberRepository1);
System.out.println("orderService -> memberRepository = " + memberRepository2);
System.out.println("memberRepository = " + memberRepository);
Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
}
}
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());
}
예상 결과
- 총 3번
- 스프링 컨테이너가 스프링 빈에 등록하기 위해
memberRepository()
호출 memberService()
로직에서memberRepository()
호출orderService()
로직에서memberRepository()
호출
- 스프링 컨테이너가 스프링 빈에 등록하기 위해
실제 결과
- 1번 호출
@Configuration 바이트코드 조작
스프링이 자바 코드를 조작할 수는 없음
테스트 코드
@Test
void configurationDeep(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
AnnotationConfigApplicationContext
에 파라미터로 넘겨AppConfig
도 스프링 빈이 됨AppConfig
클래스 출력 결과:bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$993824f4
- 뒤에 이상한 값들이 붙음
- 스프링이
CGLIB
라는 바이트코드 조작 라이브러리를 사용해AppConfig
클래스를 상속받은 임의의 다른 클래스를 만들어 스프링 빈에 등록한 것 - 이 임의의 다른 클래스가 싱글톤을 보장
AppConfig@CGLIB 예상 코드
@Bean
public MemberRepository memberRepository() {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
return 스프링 컨테이너에서 찾아서 반환;
} else { //스프링 컨테이너에 없으면
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 반환
}
}
- @Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환
- 없으면 생성해서 스프링 빈으로 등록하고 반환 코드가 동적으로 만들어짐
AppConfig@CGLIB
는AppConfig
의 자식 타입이기 때문에AppConfig
타입으로 조회 가능하고 문제 없이 동작
@Configuration을 제거한다면?
- 순수 AppConfig로 스프링 빈 등록
bean = class hello.core.AppConfig
- 싱글톤이 꺠짐
- 전에 예상했던대로
memberRepository
3번 출력call AppConfig.memberService call AppConfig.memberRepository call AppConfig.orderService call AppConfig.memberRepository call AppConfig.memberRepository
- @Bean만 사용해도 스프링 빈으로 등록되지만 싱글톤은 보장되지 않음
[참조] 인프런 스프링 핵심 원리 - 기본편 - 링크
끝!