스프링 컨테이너는 기본적으로 스프링 빈을 싱글톤으로 관리해주는 싱글톤 레지스트리 기능을 제공한다.
어떻게 하는걸까?
다음 AppConfig 클래스를 살펴보자.
package hdxian.hdxianspringcore;
import hdxian.hdxianspringcore.discount.DiscountPolicy;
import hdxian.hdxianspringcore.discount.FixDiscountPolicy;
import hdxian.hdxianspringcore.discount.RateDiscountPolicy;
import hdxian.hdxianspringcore.member.MemberRepository;
import hdxian.hdxianspringcore.member.MemberService;
import hdxian.hdxianspringcore.member.MemberServiceImpl;
import hdxian.hdxianspringcore.member.MemoryMemberRepository;
import hdxian.hdxianspringcore.order.OrderService;
import hdxian.hdxianspringcore.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 애플리케이션 전체를 설정하고 구성함.
// 앞으로 애플리케이션에 대한 환경 설정은 모두 이 클래스에서 수행한다.
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository()");
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
// System.out.println("call AppConfig.discountPolicy()");
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService()");
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
System.out.println("call AppConfig.orderService()");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
}
스프링 컨테이너는 @Bean 어노테이션이 붙은 메서드들을 실행해서 리턴되는 객체들을 스프링 빈으로 관리한다.
AppConfig에 선언된 메서드들을 보면, memberRepository()는 여기저기서 실행되므로 MemoryMemberRepsitory 객체는 여러개 생성돼야 정상이다.
.
스프링 빈 출력 테스트
테스트코드를 짜서 스프링 빈을 출력해보면, 그럼에도 불구하고 싱글톤으로 잘 관리되고 있음을 알 수 있다.
테스트를 위해 MemberServiceImpl, OrderServiceImpl에 getMemberRepository() 메서드를 임의로 추가했다.
import hdxian.hdxianspringcore.AppConfig;
import hdxian.hdxianspringcore.member.MemberRepository;
import hdxian.hdxianspringcore.member.MemberServiceImpl;
import hdxian.hdxianspringcore.order.OrderServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ConfigurationSingletonTest {
@Test
void configurationTest() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// 편의상 구현 클래스에만 따로 getMemberRepository()를 정의했기 때문에 Impl 형태로 가져옴.
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
MemberRepository repo = ac.getBean("memberRepository", MemberRepository.class);
MemberRepository repo1 = memberService.getMemberRepository();
MemberRepository repo2 = orderService.getMemberRepository();
// 둘이 같은 객체를 참조하고 있음. 즉 MemberRepository 빈은 싱글톤으로 관리되는 중.
System.out.println("repo1 = " + repo1);
System.out.println("repo2 = " + repo2);
// 얘도 위 2개 객체랑 같은 인스턴스임. 싱글톤임.
System.out.println("repo = " + repo);
Assertions.assertThat(memberService.getMemberRepository()).isEqualTo(repo);
Assertions.assertThat(orderService.getMemberRepository()).isEqualTo(repo);
}
}
테스트 결과. 같은 MemoryMemberRepository 객체가 리턴된다. (싱글톤)
위의 AppConfig 클래스에도 역시 테스트를 위해 각 메서드에 print문을 추가했다.
테스트 결과, memberRepository()는 한 번만 호출되었음을 알 수 있다.
스프링도 결국 자바 기반의 프레임워크기 때문에, 근본적인 자바 코드의 실행을 조작할 수는 없다.
근데도 memberRepository()는 한 번만 실행되었다. 뭔가 다른 수를 쓴거다.
@Configuration과 싱글톤
스프링 컨테이너는 스프링 빈의 싱글톤을 보장하기 위해 바이트코드 조작 라이브러리를 활용한다.
관련 작업은 @Configuration을 통해 설정되는데, AppConfig 클래스 내용을 찍어보면 다음과 같다.
@Test
void configurationAnnotationTest() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean);
}
찍힌 클래스 정보를 보면, 일반적인 클래스 정보가 아니다.
평범한 AppConfig 클래스였다면 AppConfig@aa11bb22 등의 평범한 정보가 나와야 한다.
@Configuration 어노테이션이 붙은 AppConfig 클래스는,
스프링 빈으로 등록될 때 AppConfig의 인스턴스 그대로 등록되지 않는다.
CGLIB이라는 바이트코드 조작 라이브러리를 이용해 AppConfig의 자식 클래스를 하나 생성하고,
그 클래스를 스프링 빈으로 등록한다.
이러한 과정으로 생성된 자식 클래스가, 다른 스프링 빈들을 싱글톤으로 관리하는 코드를 동적으로 추가하여 스프링 빈을 생성하는 원리다.
+)
AppConfig@CGLIB은 AppConfig의 자식 클래스이므로 테스트 코드에서 조회가 가능하다.
@Configuration 제거
AppConfig 클래스의 @Configuration을 제거(주석처리)하고 테스트를 돌려보면, memberRepository() 메서드가 다시 여러번 호출되는 것을 확인할 수 있다.
결론
@Bean만 사용해도 스프링 빈으로 등록은 된다.
하지만 memberRepository()처럼 의존성에 의한 메서드 호출에서 싱글톤을 보장하지 않는다.
-> (새 인스턴스가 생성됨. 이렇게 생성된 인스턴스는 스프링 빈도 아님.)
싱글톤 보장은 @Configuration의 역할이다.
근데 @Configuration을 쓸지 말지 고민하는 경우가 있을까?
그냥 원리만 이해하고, @Configuration은 항상 붙이면 된다.
'[inflearn] 스프링 핵심 원리 - 기본편 > 섹션 5 - 싱글톤 컨테이너' 카테고리의 다른 글
싱글톤 컨테이너 (1) | 2024.04.20 |
---|---|
싱글톤 패턴 (Singleton Pattern) (0) | 2024.04.20 |