목표

1. 스프링이 싱글톤을 보장하기 위해 @Configuration 애노테이션을 사용하는 이유를 배운다. 

'싱글톤 컨테이너' 목차

1. 웹 애플리케이션과 싱글톤

2. 싱글톤 패턴 

3. 싱글톤 컨테이너 

4. 싱글톤 방식의 주의점 

5. @Configuration과 싱글톤  (이번 포스팅)

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


5. @Configuration과 싱글톤

아래는 스프링 컨테이너를 만들 때 쓰는 애노테이션 기반 자바 설정 정보 AppConfig.class 클래스 파일이다. 

이상한게 하나 있다. 정말 싱글톤을 보장하고 있을까? @Bean 을 붙여서 빈을 생성하는 코드를 차례로 보자. 

1) memberService 빈을 생성하는 코드를 보자.

-> MemberServiceImpl 을 생성하자 -> memberRepository()를 호출하게 된다.  

memberRepository() 빈을 만드려고 보니 MemoryMemberRepository() 호출하게 된다. 

정리하면,

@Bean memberService 빈 생성 -> memberRepository() 호출 -> MemoryMemberRepository() 호출

 

2) orderService 빈을 생성하는 코드를 보자.

-> orderServiceImpl 생성하려고 보니 memberRepository() 와 discountPolicy() 를 호출한다. 

memberRepository()를 호출하면  MemoryMemberRepository() 호출된다. 

? 이상한데. MemoryMemberRepository()가 2번 호출되서 객체가 2개 생성된 것 같다! 

 

정리하면,

@Bean orderService 빈 생성 -> memberRepository() 호출 -> MemoryMemberRepository() 호출

 

3) 싱글톤이 보장되는지 객체를 출력해서 동일한지 테스트 코드 configurationTest() 를 작성해보자 

MemberServiceImpl 에 생성된 memberRepository가  orderServiceImpl 에 생성된 memberRepository와 같은 객체일까? 

구현체 내부에 생성된 memberRepository 객체를 비교했다. 테스트 결과, 같은 인스턴스가 공유되어 사용됨을 확인했다. 

memberRepository() 생성 호출이 안 되고 있는건 아닐까? 

 

4) AppConfig.class에 println()출력을 찍어두고 테스트 코드 configurationTest() 를 실행해보자. 

memberRepository 가 생성되는 지점이 어디일까?

AppConfig.class 자바 코드를 보면,  "call AppConfig.memberRepository" 가 3번 출력될 것이다. 

  * memberService 빈 생성할 때 memberRepository가 1번 호출된다.  

  * MemberServiceImple 을 생성할 때 1번 호출된다. 

  * orderService 빈 생성할 때 1번 호출되기 때문에 총 3번 출력 될 것이다. 

테스트를 실행해보니 반전 결과다. 

"call AppConfig.memberRepository" 가 딱 1번만 출력된다. 그러니까 각 스프링빈이 1번만 생성된다. 싱글톤이 보장되고 있었다.

왜그럴까 ? 다음 강의에서 알아보자. 


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

스프링 컨테이너는 싱글톤 레지스트리다. 그러니까 스프링 빈이 싱글톤이 되도록 보장해야 한다. 자바 코드만 봤을 때 분명 3번 호출되어야 맞는데. 어떻게 memberRepository 객체를 1번만 생성한걸까?

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

 

1) AppConfig.class 가 순수한 클래스로 만든 스프링 빈으로 등록된다? 

AppConfig 도 스프링 빈으로 등록되는데. 생성된 AppConfig 빈의 타입을 getClass() 메서드로 출력해보자. 

순수한 스프링 빈이라면, 내가 만든 AppConfig 클래스는 hello.core.AppConfig 라고 출력되어야 하는데. 

$$EnhancerBySpringCGLIB 라는 이름이 붙어있다. 

내가 만든 클래스가 아닌걸까? 이유를 알아보자. 

 

2) 스프링이 만든 AppConfig@BySpringCGLIB 클래스가 스프링 빈으로 등록된다!

 

스프링은 @Configuration이 붙은 클래스를 읽으면, 그 클래스를 그대로 스프링 클래스로 만들지 않는다.

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

클래스 이름에는 BySpringCGLIB 이런 이름이 붙는다. 

스프링이 만든 임의의 클래스가 스프링 빈으로 등록된다!

그림은 AppConfig.class를 상속받아서 AppConfig@BySpringCGLIB라는 다른 클래스를 만든 것을 표현했다. 

 

3) AppConfig@CGLIB 클래스가 어떤 역할을 할까? 

 AppConfig@CGLIB 클래스가 스프링 빈의 싱글톤을 보장한다.

(CGLIB 내부 기술을 사용하는데 매우 복잡하다.)

 

아마도 다음과 같은 로직으로 AppConfig@CGLIB 클래스가 싱글톤을 보장할 것이다. 

1.  AppConfig.class를 쭈욱 읽으면서 @Bean이 붙은 메서드마다 빈을 생성한다

2. 이미 스프링 빈이 존재하면, 스프링 컨테이너에 존재하는 빈을 반환한다. 

3. 컨테이너에 스프링 빈이 없다면, AppConfig.class 의 기존 로직을 읽어서 스프링 빈을 등록하고 반환한다. 

 

[ 참고 ] 

내가 만든 AppConfig의 타입을 조회했는데. 왜 AppConfig@CGLIB 클래스 타입이 출력되었을까? 

AppConfig@CGLIB 클래스는 AppConfig를 상속받은 자식클래스이기 때문이다. 

빈 조회를 하면서 배운 내용을 떠올려보자. 부모 타입을 조회하면, 자식 타입도 전부 딸려서 조회된다. 

 

4) 만약, @Configuration 을 적용하지 않고, @Bean 만 붙여도 문제가 없을까? 

애노테이션을 주석처리 하고, 아까 작성한 configurationDeep() 테스트 코드를 실행하여 스프링 빈을 조회해보자.

에러가 나지는 않는다. 하지만 memberRepository 가 3번 호출되어 3개의 객체가 생성된다. 

memberRepository 처럼 의존관계 주입이 필요해서 메서드를 직접 호출할 때 싱글톤이 보장되지 않는다. 

5) 정리 

고민할 게 없다. @Configuration 애노테이션이 스프링 빈의 싱글톤을 보장해준다는 점이 핵심이다. 


다음 강의에서는 설정 정보 없이 자동으로 스프링빈을 등록하는 '컴포넌트 스캔'을 배운다. 

공부 내용 출처 :  스프링 핵심 원리 기본편 

728x90

+ Recent posts