[스프링 핵심원리 기본편] 5.2 @Configuration과 싱글톤

코린이서현이·2023년 11월 12일
0

🎯 목표

📌 @Configuration과 싱글톤
📌 @Configuration과 바이트코드 조작의 마법

📌 @Configuration과 싱글톤

진짜 싱글톤일까??

appConfig 코드를 보면 new MemoryMemberRepository()가 여러번 실행되는 것을 볼 수 있다.
🤔 과연 싱글톤 패턴일까?

@Bean memberService -> memberRepository()  -> new MemoryMemberRepository()
@Bean orderService() -> memberRepository() -> new MemoryMemberRepository()
                        discountPolicy()  -> new RateDiscountPolicy();

확인을 위한 메소드 추가

public class MemberServiceImpl implements MemberService {
 private final MemberRepository memberRepository;
 //테스트 용도
 public MemberRepository getMemberRepository() {
 return memberRepository;
 }
}
public class OrderServiceImpl implements OrderService {
 private final MemberRepository memberRepository;
 //테스트 용도
 public MemberRepository getMemberRepository() {
 return memberRepository;
 }
}

테스트 코드

public class ConfigurationSingletonTest {

  @Test
  void configrationTeset() {
    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
    OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
    MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);

    MemberRepository memberRepository1 = memberService.getMemberRepository();
    MemberRepository memberRepository2 = orderService.getMemberRepository();

    //모두 같은 인스턴스를 참고하고 있다.
    System.out.println("memberService -> memberRepository = " + memberRepository1);
    System.out.println("orderService -> memberRepository = " + memberRepository2);
    System.out.println("memberRepository = " + memberRepository);

    assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
    assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
  }
}

💻 실행화면

memberService -> memberRepository = hello.core.member.MemoryMemberRepository@b4c8dbea
orderService -> memberRepository = hello.core.member.MemoryMemberRepository@b4c8dbea
memberRepository = hello.core.member.MemoryMemberRepository@b4c8dbea

👉모두 같은 Repository를 반환한다는 것을 볼 수 있다.

🔍 그러면 실행을 조금 더 자세히 알아보자.

위 코드에서 memberService는 총 세번 호출되어야할 것이다.
그러나 기록을 보면 그렇지 않다.

❣️으엥? : 기록을 살펴보자

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

memberService -> memberRepository = hello.core.member.MemoryMemberRepository@206a2c64
orderService -> memberRepository = hello.core.member.MemoryMemberRepository@206a2c64
memberRepository = hello.core.member.MemoryMemberRepository@206a2c64

➡️ memberService 가 딱 한번만 호출된다. 왜 그런걸까?
자바 코드 자체가 세번 호출 되어야하는데 그렇지 않다. 어떤 조작이 있는 것일까?

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

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

AppConfig 클래스를 한번 확인해보자.

  @Test
  void configurationDeep() {
    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    AppConfig bean = ac.getBean(AppConfig.class);

    System.out.println("bean = " + bean.getClass());
  }

🤔 당연히 실행결과는 AppConfig겠지?

💻 실행화면

bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$e2f5472c

😲 어라라? 아니네?

✔️ 사용하는 AppConfig

  • AppConfig를 상속받은 임의의 다른 클래스

AppConfig@CGLIB 예상 코드

@Bean
public MemberRepository memberRepository() {

 if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
 return 스프링 컨테이너에서 찾아서 반환;
 } else { //스프링 컨테이너에 없으면
 기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
 return 반환
 }

➡️ 스프링 빈에 등록이 되어있으면, 스프링 컨테이너에서 찾아서 반환하기 때문에,위 코드에서 memberService이 한번만 호출이 되는 것이다.
(우리는 실제로 오버라이딩된 AppConfig@@@를 호출!!)

✔️ 덕분에 싱글톤이 보장되는 것이다.

🤔 @Configuration을 안붙이면?

  • @Bean으로 스프링 빈을 등록하지만,
  • 메서드를 직접 호출할 때 싱글톤을 보장하지 않는다.

⚠️ 오류 발생

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No bean named 'memberRepository' available
ctrl + shift + r
profile
24년도까지 프로젝트 두개를 마치고 25년에는 개발 팀장을 할 수 있는 실력이 되자!

0개의 댓글