@Configuration
@Configuration은 단순히 설정 클래스를 나타내는 게 아니라, Spring이 내부에서 CGLIB 프록시를 적용할 수 있도록 의도적으로 명시하는 어노테이션입니다. 이 덕분에 Spring은 Java 코드로도 안전하게 싱글톤 빈을 구성할 수 있습니다.
읽기 전에 알아두면 좋은 점
- Spring은 기본적으로 모든 빈을 싱글톤으로 관리합니다.
- 이를 통해 메모리 낭비를 줄이고, 같은 객체를 재사용함으로써 성능과 일관성을 확보할 수 있습니다.
- @Bean 메서드는 Spring 컨테이너에 객체를 등록하는 방법이지만, @Bean만으로는 메서드 간 호출 시 싱글톤 보장이 어렵습니다.
- @Configuration은 메서드 단위가 아니라 클래스 단위로 적용됩니다.
- @Configuration을 클래스에 붙여야, Spring이 해당 클래스에 CGLIB 프록시를 적용하여 내부 호출도 싱글톤으로 보장합니다.
- @Bean 하나로만으로는 소용없습니다. 클래스에 @Configuration을 붙여줘야합니다.
- 따라서 개발자는 객체 생명주기 걱정 없이 의존성 주입을 구현할 수 있습니다.
1. 문제의 출발점
Spring에서 @Bean 메서드를 여러 개 정의할 때, 서로 호출하면서 의존 객체를 생성합니다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
이 코드를 일반적인 Java 코드로 생각하면 memberRepository()가 두 번 호출되어 서로 다른 인스턴스를 반환할 것처럼 보입니다. 하지만 실제 실행 결과는 동일한 싱글톤 객체가 반환됩니다.
이 싱글톤 보장을 가능하게 하는 것이 바로 @Configuration + CGLIB 프록시입니다.
2. Spring의 해결 전략: CGLIB 프록시
Spring은 @Configuration이 붙은 클래스를 처리할 때, 해당 클래스를 CGLIB을 이용해 프록시 객체로 감쌉니다.
CGLIB이란?
- Code Generation Library의 약자
- 런타임에 클래스를 상속하고, 메서드를 오버라이딩하여 동작을 수정하는 기술
- Spring은 이를 이용해 메서드 호출을 가로채고, 빈 컨테이너에서 동일한 객체를 반환하게 합니다.
3. 프록시 내부 동작
Spring이 @Configuration 클래스를 프록시로 감싸면, AppConfig는 내부적으로 다음과 같은 형태가 됩니다.
class AppConfig$$EnhancerBySpringCGLIB extends AppConfig {
@Override
public MemberRepository memberRepository() {
// 매번 새로 생성하지 않고, 스프링 컨테이너에서 가져옴
return getBeanFromContainer("memberRepository");
}
}
즉, memberRepository()를 직접 호출해도 실제로는 Spring 컨테이너에서 이미 등록된 싱글톤 빈을 꺼내서 반환합니다.
4. 확인 방법
실제로 프록시 클래스가 만들어졌는지 확인하려면 다음 테스트 코드를 작성해야합니다.
@Test
void configurationClassTest() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig appConfig = ac.getBean(AppConfig.class);
System.out.println("appConfig.getClass() = " + appConfig.getClass());
}
출력 결과
appConfig.getClass() = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$1a2b3c4d
$$EnhancerBySpringCGLIB$$ 라는 문자열이 붙은 것으로 보아 CGLIB 프록시 클래스임을 알 수 있습니다.
5. CGLIB이 적용되지 않는 경우
① @Configuration을 생략한 경우
@Component
public class AppConfig {
...
}
- @Bean은 동작하지만, 프록시가 적용되지 않아 memberRepository()를 호출할 때마다 새 객체가 생성될 수 있습니다.
② @Bean 메서드에 static 키워드를 붙인 경우
@Bean
public static MemberRepository memberRepository() { ... }
- static 메서드는 오버라이딩 대상이 아니기 때문에, 프록시가 이를 가로챌 수 없습니다.
- 결과적으로 싱글톤 보장 실패.
6. 정리
항목 | 설명 |
@Configuration | 해당 클래스에 CGLIB 프록시를 적용하여 @Bean 메서드를 오버라이드함 |
CGLIB 프록시 | 메서드를 가로채어 싱글톤 빈을 반환하게 함 |
왜 필요한가? | @Bean 메서드 간 직접 호출이 발생해도 싱글톤을 보장하기 위함 |
주의사항 | @Configuration 생략, static 메서드 사용 시 CGLIB 프록시 동작 안 함 |
'Dev Framework > Spring' 카테고리의 다른 글
[Spring] HttpServlet 완벽 정리 (0) | 2025.03.31 |
---|---|
[Spring] 스프링 AOP - 1 (0) | 2025.03.28 |
[JPA] JPA 영속성 컨텍스트 with 프록시 - 2 (0) | 2025.03.23 |
[JPA] JPA 영속성 컨텍스트 완전 정복 - 1 (0) | 2025.03.23 |
[SpringBoot] Lombok 사용 시 부모 생성자 호출 문제와 해결 방안 (0) | 2024.05.15 |