목표
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 애노테이션이 스프링 빈의 싱글톤을 보장해준다는 점이 핵심이다.
다음 강의에서는 설정 정보 없이 자동으로 스프링빈을 등록하는 '컴포넌트 스캔'을 배운다.
공부 내용 출처 : 스프링 핵심 원리 기본편
'프로그래밍 > Spring Basic' 카테고리의 다른 글
13. 컴포넌트 스캔의 필터와 빈 이름 중복 (0) | 2021.12.25 |
---|---|
12. 컴포넌트 스캔과 의존관계 자동 주입 시작하기 (0) | 2021.12.25 |
10. 싱글톤 컨테이너와 싱글톤 방식의 주의점 (0) | 2021.12.24 |
9. 스프링 컨테이너의 다양한 설정 형식 - 자바 코드, XML (0) | 2021.12.24 |
8. 스프링 빈 조회 - 상속 관계 (0) | 2021.12.23 |