본문 바로가기
Learning-log/Spring & JPA

(스프링 핵심 원리 - 기본편) 5-(5)@Configuration과 싱글톤 (6)@Configuration과 바이트코드 조작의 마법

by why제곱 2023. 4. 4.

- @Configuration과 싱글톤 

  • @Bean memberService 를 호출하면 memberRepository() 를 호출하면서,  new MemoryMemberRepository() 호출
  • @Bean orderService()를 호출하면 memberRepository()와 discountPolicy()를 호출하면서, new new MemoryMemberRepository() 와 new RateDiscountPolicy() 호출

위 과정을 살펴보면 마치 싱글톤이 깨지는 것처럼 보임. 스프링 컨테이너는 이를 어떻게 해결할까? 

  • 실험을 해보자 ! 
  • 테스트용으로 MemberServiceImpl과 에 getMemberRepository() 메서드 생성

 

 

  • 위 테스트 결과 세 객체가 모두 같은 객체임을 확인할 수 있다. 
  • AppConfig를 보면 각각 2번 new 를 통해 새로운 다른 인스턴스가 호출되어 사용되어야 할 것 같은데 왜 같은 객체인걸까?
  • AppConfig에 아래와 같이 "call AppConfig.~"를 sysout하여 이 AppConfig의 각각의 메서드가 몇번 호출 되는지 직접 눈으로 확인보자.
package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@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());
    }
    @Bean
    public DiscountPolicy discountPolicy() {
        System.out.println("call AppConfig.discountPolicy");
        return new RateDiscountPolicy();
    }
}
  • 위 코드로 수정하고 ConfigurationTest를 실행하면, 그 결과는 아래와 같다.

  • 실제로 호출될 거라고 생각했던 횟수와 달리 call이 세번밖에 등장하지 않았다 => 스프링 컨테이너가 어떻게든 싱글톤을 유지하고 지켜준다는 뜻 .
  • 예상결과
// call AppConfig.memberService
// call AppConfig.memberRepository
// call AppConfig.memberRepository
// call AppConfig.orderService
// call AppConfig.memberRepository

 

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

  • 아래 테스트를 살펴보자.

  • bean.getClass의 결과가 순수한 클래스라면 'class hello.core.AppConfig' 와 같이 출력되어야 함
  • 하지만 뒤에 부가적으로 무언가가 더 붙어서 출력됨
  • 내가 만든 클래스가 아니라 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig라는 클래스를 상속받은 임의의 다른 클래스를 만들고 그 다른 클래스를 스프링 빈으로 등록한 것!
  • 이 임의의 다른 클래스가 싱클톤이 보장되도록 해줌.
  • 그럼 실제로 호출된 클래스는 다른 클래스인데 어떻게 AppConfig.class로 조회가 되었는가?
    • AppConfig를 상속받은 클래스이기 때문에 부모타입을 조회하면 자식타입이 모두 끌려나오므로 AppConfig.class로 조회를 해도 바이트코드 조작 라이브러리를 통해 생성된 임의의 다른 클래스가 호출될 수 있었던 것.
    • @Configuration을 붙이지 않고 @Bean만 있으면 어떻게 될까?
      • 임의의 다른 클래스가 생성되지 않음. CGLIB 사용되지 않음
      • 그러면서, 내가 싱글톤이 아닐 수도 있지 않을까 의문을 제기하며 나올거라 예상했던 'call ~ '의 결과가 그대로 출력됨. ( 싱글톤이 깨짐)
      • @Autowired 를 하면 의존관계 주입이라해서 이걸로 해결할 수 있음(뒤에서 더 다룰 예정)