지난 편에서는 스프링이 어떻게 싱글톤 객체를 자동으로 관리해주는지, 그리고 싱글톤 설계 시 주의할 점들을 다뤘다.
이번 편에서는 @Configuration이 어떻게 실제로 싱글톤을 보장하는지, 그리고 그 뒤에 숨어 있는 바이트코드 조작의 비밀에 대해 살펴보겠습니다람쥐~
1. AppConfig의 의도와 실제 호출
다음은 우리가 일반적으로 작성하는 AppConfig 코드입니다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
이 코드를 보면 memberRepository()가 3번 호출될 것처럼 보입니다. 하지만 실제로는 딱 한 번만 호출됩니다. 이유가 뭘까요?
2. 스프링의 마법: @Configuration + CGLIB
스프링은 @Configuration이 붙은 클래스를 처리할 때, 해당 클래스를 상속받아 프록시 객체를 생성합니다. 이 프록시 객체는 CGLIB이라는 바이트코드 조작 라이브러리를 이용해 만들어집니다.
AppConfig$$EnhancerBySpringCGLIB$$...
클래스 이름 뒤에 이상한 이름이 붙는다면 CGLIB 프록시가 생성된 것이라는 뜻입니다.
3. 프록시 객체의 역할
프록시로 생성된 AppConfig는 @Bean 메서드를 호출할 때 다음과 같은 동작을 합니다.
- 해당 빈이 이미 스프링 컨테이너에 존재하면 → 기존 객체 반환
- 존재하지 않으면 → 생성하고 등록한 뒤 반환
이러한 로직이 동적으로 프록시 내부에 삽입되어 있어, memberRepository()가 여러 번 호출되더라도 항상 같은 인스턴스를 반환하게 됩니다.
4. 테스트로 확인해보기
아래는 실제로 싱글톤이 보장되는지 확인하는 테스트입니다.
@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 repo1 = memberService.getMemberRepository();
MemberRepository repo2 = orderService.getMemberRepository();
assertThat(repo1).isSameAs(memberRepository);
assertThat(repo2).isSameAs(memberRepository);
}
실행 결과
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.orderService
memberRepository()는 한 번만 호출되며, 모든 빈이 같은 객체를 공유하고 있습니다.
5. @Configuration을 생략하면?
만약 @Configuration을 제거하고 @Bean만 붙인 클래스라면 어떻게 될까요?
public class AppConfig {
...
}
이 경우, CGLIB 프록시가 적용되지 않기 때문에 각 메서드가 호출될 때마다 새로운 객체가 생성됩니다. 즉, 싱글톤이 깨지게 되는 것이죠.
설정 클래스에는 항상 @Configuration을 붙여야 싱글톤이 제대로 보장됩니다.
정리
- @Configuration은 단순한 어노테이션이 아니라, CGLIB 프록시를 통해 싱글톤을 보장하는 핵심 메커니즘입니다.
- @Bean 메서드는 스프링이 자동으로 프록시로 감싸기 때문에, 개발자가 신경 쓰지 않아도 하나의 객체만 생성됩니다.
- @Configuration이 없다면 이러한 보장이 깨지므로, 설정 클래스에는 반드시 붙여야 합니다.
'Spring' 카테고리의 다른 글
[Spring 완전 정복 시리즈] 16편 - 컴포넌트 스캔 심화 (0) | 2025.07.29 |
---|---|
[Spring 완전 정복 시리즈] 15편 - 컴포넌트 스캔의 시작 (0) | 2025.07.29 |
[Spring 완전 정복 시리즈] 13편 - 싱글톤 컨테이너의 원리와 설계 주의점 (1) | 2025.07.28 |
[Spring 완전 정복 시리즈] 12편 - 다양한 빈 조회와 스프링 컨테이너의 숨겨진 기능들 (2) | 2025.07.27 |
[Spring 완전 정복 시리즈] 11편 - 스프링 빈 조회와 ApplicationContext의 역할 (1) | 2025.07.27 |