1. @Configuration과 싱글톤의 의문점
AppConfig 코드를 보면 memberRepository()가 여러 번 호출되는 구조로 인해 싱글톤이 깨질 것처럼 보임
[코드] 의문이 발생하는 AppConfig
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository()); // memberRepository() 호출
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy()); // memberRepository() 호출
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository(); // 여기서 new가 총 3번 실행될까?
}
// ... 생략
}
- 추측: memberService에서 1번, orderService에서 1번, 그리고 스프링이 빈을 등록할 때 1번. 총 3개의 인스턴스가 생성되어야 음
2. 검증을 위한 코드 추가 및 테스트
정말로 인스턴스가 여러 개 생기는지 확인하기 위해 테스트 용도의 메서드를 추가하고 테스트 코드를 실행
[코드] 검증 용도 코드 추가 (Service 구현체)
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;
}
}
[코드] 싱글톤 확인 테스트 코드
package hello.core.singleton;
import hello.core.AppConfig;
import hello.core.member.MemberRepository;
import hello.core.member.MemberServiceImpl;
import hello.core.order.OrderServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.*;
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);
// 모두 같은 인스턴스를 참고하고 있다.
System.out.println("memberService -> memberRepository = " + memberService.getMemberRepository());
System.out.println("orderService -> memberRepository = " + orderService.getMemberRepository());
System.out.println("memberRepository = " + memberRepository);
assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
}
}
- 결과: 모든 memberRepository 인스턴스는 동일합니다. 주소값이 모두 같음
3. 호출 로그 실험
정말로 메서드가 여러 번 호출되는지 확인하기 위해 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();
}
// ...
}
- 출력 결과: 예상과 달리 모두 1번만 호출됨
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.orderService
4. @Configuration과 바이트코드 조작의 마법
스프링은 자바 코드의 로직을 그대로 두지 않고, CGLIB라는 바이트코드 조작 라이브러리를 사용하여 싱글톤을 보장
[코드] AppConfig 클래스 정보 확인 테스트
@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$$EnhancerBySpringCGLIB$$bd479d70
}
- 클래스 명에 CGLIB가 붙어 있음
- 이는 스프링이 AppConfig를 상속받은 임의의 프록시 클래스를 만들어 빈으로 등록했음을 의미

CGLIB 예상 로직 (AppConfig@CGLIB)
@Bean
public MemberRepository memberRepository() {
if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
return 스프링 컨테이너에서 찾아서 반환;
} else { // 스프링 컨테이너에 없으면
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 반환;
}
}
5. @Configuration을 적용하지 않는다면?
만약 @Configuration을 빼고 @Bean만 사용하면 어떻게 될까요?
[코드] @Configuration 삭제 실험
// @Configuration 삭제
public class AppConfig {
@Bean
public MemberService memberService() { ... }
@Bean
public MemberRepository memberRepository() { ... }
// ...
}
- 출력 결과 (클래스): bean = class hello.core.AppConfig (순수 자바 클래스)
- 출력 결과 (로그): memberRepository()가 총 3번 호출됩니다.
- 테스트 결과: 각각 다른 MemoryMemberRepository 인스턴스를 가지게 되어 싱글톤이 깨집니다.
memberService -> memberRepository = @6239aba6
orderService -> memberRepository = @3e6104fc
memberRepository = @12359a82
6. 정리
- @Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤은 보장되지 않음
- memberRepository()처럼 의존관계 주입이 필요해서 메서드를 직접 호출할 때 문제가 발생
- 스프링 설정 정보에는 항상 @Configuration을 사용하여 싱글톤을 보장받아야 함
'INFLEARN' 카테고리의 다른 글
| [스프링 핵심 원리 - 기본편] 11. 의존관계 자동 주입의 4가지 방법 (0) | 2026.03.20 |
|---|---|
| [스프링 핵심 원리 - 기본편] 10. ComponentScan (0) | 2026.03.15 |
| [스프링 핵심 원리 - 기본편] 8. 웹 애플리케이션과 Singleton (0) | 2026.03.13 |
| [스프링 핵심 원리 - 기본편] 7. BeanFactory와 ApplicationContext (0) | 2026.03.13 |
| [스프링 핵심 원리 - 기본편] 6. 스프링 빈 조회 - 기본, 동일타입이 둘 이상, 상속 (0) | 2026.03.13 |