AppConfig@CGLIB 예상 코드
...
@Bean
public MemberRepository memberRepository(){
if(memoryMemberRepository가 이미 스프링 컨테이너에 있으면?){
return 스프링 컨테이너에서 찾아서 반환;
} else {
기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 반환
}
}
...
먼저, 스프링 컨테이너의 싱글톤 유지는 @Configuration 어노테이션이 있어 가능하다는 점을 알린다.
@Configuration 어노테이션이 적용된 "AppConfig"은 스프링 컨테이너에 관리되는 빈으로 등록되는것처럼 보이지만, 실제로 스프링은 CGLIB 를 사용하여 "AppConfig" 클래스를 상속받은 프록시 클래스인 "AppConfig@CGLIB" 클래스를 생성하고, 이 프록시 클래스를 빈으로 등록한다.
따라서 "AppConfig@CGLIB" 라는 클래스가 "AppConfig" 라는 이름의 빈으로 스프링 컨테이너에 등록되며, 이 프록시 클래스는 원본 "AppConfig" 를 확장하여 의존성 주입 등의 기능을 제공한다. "Bean" 으로 어노테이션된 메서드들은 이 프록시 클래스 내에서 호출되어 관리되고, 빈은 싱글톤으로 동작한다.
@Configuration 어노테이션 없이 @Bean 만 있다면, AppConfig@CGLIB 가 스프링 빈으로 등록되는게 아니라 AppConfig 가 스프링 빈으로 등록된다.
따라서 AppConfig@CGLIB 의 스프링 빈에 객체가 등록되어 있을때 등록된 객체를 내보내는 기능이 AppConfig 엔 없기 때문에 @Bean 에 객체가 등록되어도 그냥 새로운 객체를 생성하여 내보낸다. 따라서 싱글톤이 깨진다!
정리하자면, @Bean 만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지는 않는다.
=> 스프링 설정 정보는 항상 "@Configuration" 을 사용하자!
@Bean 만 사용했을 때 싱글톤이 깨지는 경우의 예시)
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);//빈 등록할때 한번씩 다 호출된다!
위와 같은 스프링 컨테이너 생성문이 호출되었을때,
아래의 첫번째 순서는 @Configuration + @Bean 이 있을때, 두번째 순서는 @Configuration 없이 @Bean 만 있을때이다.
*discountPolicy() 호출은 제외하였다.
순서1)
call AppConfig.memberService
calL AppConfig.memberRepository
call AppConfig.orderService
순서2)
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.memberRepository
call AppConfig.orderService
call AppConfig.memberRepository
AppConfig.java
@Configuration
public class AppConfig {
/*싱글톤이 아닐때 아래 메서드들을 한번씩 호출할때(discountPolicy 제외)*/
//call AppConfig.memberService
//call AppConfig.memberRepository
//call AppConfig.memberRepository
//call AppConfig.orderService
//call AppConfig.memberRepository
/*싱글톤일때 ...*/
//call AppConfig.memberService
//calL AppConfig.memberRepository
//call AppConfig.orderService
//생성자 주입 방법
@Bean //스프링 컨테이너에 메서드이름을 key, 리턴값을 value 로 하여 등록됨
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());//구현클래스(구현)
}
@Bean
public static DiscountPolicy discountPolicy() {//인터페이스(역할)
return new RateDiscountPolicy();//구현클래스(구현)
}
}
본인
나는 AppConfig 클래스가 빈으로 등록되는게 아니라 AppConfig 클래스를 상속받은 AppConfig@CGLIB 클래스가 AppConfig 라는 이름으로 빈에 등록된다고 알고 있는데 내가 틀린거야?
ChatGPT
죄송합니다, 이해를 잘못하고 있었습니다. 당신이 설명하신 대로 맞습니다.
AppConfig 클래스가 @Configuration 어노테이션이 적용되어 있고, 스프링 컨테이너에서 관리되는 빈으로 등록됩니다. 그러나 실제로 스프링은 CGLIB를 사용하여 AppConfig 클래스를 상속받은 프록시 클래스인 AppConfig@CGLIB를 생성하고, 이 프록시 클래스를 빈으로 등록합니다.
따라서 AppConfig@CGLIB라는 클래스가 AppConfig라는 이름의 빈으로 스프링 컨테이너에 등록되며, 이 프록시 클래스는 원본 AppConfig를 확장하여 의존성 주입 등의 기능을 제공합니다. @Bean으로 어노테이션된 메서드들은 이 프록시 클래스 내에서 호출되어 관리되고, 빈은 싱글톤으로 동작합니다.
정확히 말씀드리면, AppConfig 클래스 자체는 빈으로 등록되지 않습니다. 대신, 이 클래스를 상속받은 프록시 클래스 AppConfig@CGLIB가 AppConfig라는 이름의 빈으로 등록되는 것이 맞습니다. 이 프록시 클래스는 스프링이 빈의 생성과 의존성 주입을 관리하는데 사용됩니다.
다시 한 번, 제가 이전에 드린 설명이 잘못되었고, 당신이 말씀하신 것이 맞습니다. 감사합니다.