Loading [MathJax]/jax/output/HTML-CSS/jax.js

김영한님의 스프링 핵심 원리 - 기본편 강의
섹션5. 싱글톤 컨테이너 강의 요약입니다.

@Configuration과 싱글톤

아래 코드를 보자. BEAN이 어떤 순서로 생성될까? 순차적으로 scan하니 우리가 예상하는 대로 작성해보자.

예상되는 호출 순서


  
call AppConfig.memberService // memberService에서
call AppConfig.memberRepository // memberRepository호출하고,
call AppConfig.memberRepository // memberRepository 가 또 호출되고
call AppConfig.orderService // orderService 가 호출되면서
call AppConfig.memberRepository //memberRepository가 호출된다.

  
@Configuration
public class AppConfig {
/**
* 생성자 주입
*/
@Bean
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());
}
...
}

AppConfig.class에서 각각 두개의 MemoryMemberRepository를 생성하면서 Singleton이 깨지는 것처럼 보인다. 그러나 테스트 코드를 돌리면 MemoryMemberRepository가 한 번만 호출되고, 모두 같은 인스턴스가 공유되어 사용된다.


  
// 다음 코드를 실행해보자.
@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}

결과

AppConfig자바 코드를 보면 분명히 다른 인스턴스가 생성되는데 로그를 출력하면 bean 생성시 call AppConfig.memberRepository 한 줄만 출력된다.


  
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.orderService

@Configuration과 바이트코드 조작의 마법

AppConfig bean 클래스 이름을 출력해보면 다음과 같은 클래스 이름이 출력된다. bean = class hello.core.AppConfig$$SpringCGLIB$$0
내가 만든 것은 AppConfig 자체인데, 뒤에 SpringCGLIB.(은 @ 와 같다.)

스프링이 CGLIB 라는 바이트코드 조작 라이브러리를 사용해서 AppConfig클래스를 상속받은 임의의 다른 클래스를 만들고,그 다른 클래스를 스프링 빈으로 등록한 것이다.
즉, 스프링 컨테이너에 등록된 AppConfig는 이름은 AppConfig인데 instance가 AppConfig$$SpringCGLIB 이다.

AppConfig$$SpringCGLIB

  • 임의의 다른 클래스가 바로 싱글톤이 보장되도록 해준다.
  • 바이트 코드를 조작해서 작성되어 있을 것이다. (실제로는 매우 복잡)
  • @Bean이 붙은 메서드마다 스프링 빈이 존재하면 존재하는 빈 반환, 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어짐
  • 덕분에 싱글톤이 보장된다.

AppConfig$$SpringCGLIB 예상 코드


  
@Bean
public MemberRepository memberRepository();
if (memberRepository가 이미 스프링 컨테이너에 등록되어 있으면)
return 스프링 컨테이너에서 찾아서 반환
else //스프링 컨테이너에 없으면
return 기존 로직을 호출해서 MemoryMemberRepository 생성하고 반환함

@Configuration 어노테이션을 제외해도 Bean 생성은 된다.

But 스프링 빈으로는 등록되지만, 싱글톤은 보장하지 않는다. 스프링 설정 정보는 항상 @Configuration을 사용하자!

@Configuration 제외하고, 같은 테스트 코드 실행했으나 싱글톤이 깨지는 것을 볼 수 있음(memberRepository가 3번 호출됨)
호출되는 bean도 AppConfig 그 자체임 -> bean = class hello.core.AppConfig


  
call AppConfig.memberService
call AppConfig.memberRepository
call AppConfig.memberRepository
call AppConfig.orderService
call AppConfig.memberRepository

  
// 다음 코드를 실행해보자.
@Test
void configurationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}

그리고 착한 Intellij는 @Configuration 을 제외했을 경우, 잘못되었다고 알려준다.
image