[Spring] @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 FixDiscountPolicy();
        return new RateDiscountPolicy();
    }

}

memberService 빈을 만드는 코드를 보면 memberRepository()를 호출하는데, 이 메서드는 new MemoryMemberRepository() 객체를 생성한다. 동일하게 OrderService 빈을 만드는 코드 또한 memberRepository()를 호출하는데, 이 메서드도 new MemoryMemberRepository() 객체를 생성한다. 

결과적으로 각각 다른 2개의 MemoryMemberRepository가 생성되면서 싱글톤이 깨지는 것처럼 보인다. 스프링 컨테이너는 이 문제를 어떻게 해결할까?

 

@Configuration과 바이트 조작

 스프링 컨테이너는 싱글톤 레지스트리이다. 따라서 스프링 빈이 싱글톤이 되도록 보장해주어야 한다. 

그래서 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용한다. 

 

다음 코드를 보자.

@Test
 void configurationDeep() {
    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    //AppConfig도 스프링 빈으로 등록된다.
    
    AppConfig bean = ac.getBean(AppConfig.class);
    System.out.println("bean = " + bean.getClass());
    //출력: bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$bd479d70
}

AnnotationConfigApplicationContext에 파라미터로 넘긴 값은 스프링 빈으로 등록된다. 그래서 AppConfig도 스프링 빈이 된다. AppConfig 스프링 빈을 조회해서 클래스 정보를 출력해보자. 

이와 같이 출력되는 것을 확인할 수 있다. 

 

만약 순수한 클래스라면 다음과 같이 출력되어야 한다. 

그런데 예상과 달리 뒤에 xxxxCGLIB가 붙으면서 상당히 복잡해진 것을 확인할 수 있다. 

이것은 내가 만든 클래스가 아니라 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것이다. 

 

말이 좀 복잡한데 이해하기 쉽게 그림으로 확인해보자. 

이 임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다. 

@Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 스프링 빈이 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어진다. 덕분에 싱글톤이 보장되는 것이다. 

참고 : AppConfig@CGLIB는 AppConfig의 자식 타입이므로, AppConfig 타입으로 조회할 수 있다. 

 

그럼, @Configuration을 적용하지 않고, @Bean만 적용하면 어떻게 될까?

이렇게 사용하면 AppConfig가 CGLIB 기술 없이 순수한 AppConfig로 스프링 빈에 등록된다. 하지만, 싱글톤을 보장하진 않는다. 

 

따라서, 고민할 필요없이 스프링 설정 정보는 항상 @Configuration을 사용하자!