[공부 정리] Spring 핵심원리 기본편 - 싱글톤 컨테이너

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

의문점

기존 AppConfig 를 작성한 내용을 보면 new MemberRepository()를 OrderService, MemberService 를 각각 Bean에 등록할 때 호출한다. 싱글톤 객체는 오직 유니크한 하나의 객체를 생성하는 것인데 new가 여러 번 호출하게 되면 싱글톤 패턴이 깨지는 것이 아닌 지, 이를 스프링에선 어떻게 관리하고 있는 지 테스트 하면서 동작을 확인함.

// @Bean memberService -> new MemoryMemberRepository()
// @Bean orderService  -> new MemoryMemberRepository(
// 얼핏 봤을 때 싱글톤 패턴이 깨진 것처럼 2번이 호출되고 있다.
@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(), dicsountPolicy());
}

 

 

테스트 동작 시 같은 객체임을 확인할 수 있다. 즉 한번만 new 를 통해 생성이 된 것.

스프링에서 CGLIB라는 바이트 코드 조작 라이브러리를 사용해서 AppConfgig 클래스를 상속 받은 임의의 다른 클래스를 만듦.
 System.out.println("appConfig.getClass() = " + appConfig.getClass());

 출력을 보면 CGLIB가 붙어있는 걸 볼 수 있음.

CGLIB

Byte Code Generation Library is high level API to generate and transform JAVA byte code. It is used by AOP, testing, data access frameworks to generate dynamic proxy objects and intercept field access.

 

AOP, 프레임워크에서 동적 프록시 객체를 생성하고 필드 액세스를 가로채는 데 사용한다.

-> CGLIB 라이브러리에서 실제 AppConig를 상속받은 객체를 생성하여 이를 Spring 컨테이너에 Bean으로 등록하는 것이다.

 

결론

이러한 내부 로직이 @Configuration 에 있어, 싱글톤을 보장하고 있다.

Test Code

* 추가 인텔리제이에서 실행할 때 테스트 시, 내용이 깨지는 경우

도움말(help) -> 사용자 옵션 지정 VM 편집

 아래 내용을 사용자 VM 옵션에 추가해준다. 반영이 바로 안될 경우 인텔리제이 재실행하면 반영이 되어 있다.

-Dfile.encoding=UTF-8

 

package hello.core.singleton;

import hello.core.AppConfig;
import hello.core.member.MemberRepository;
import hello.core.member.MemberServiceImpl;
import hello.core.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;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

public class ConfigurationSingletonTest {

    @Test
    void configurataion(){

        // 확인해보면 같은 인스턴스가 공유되어 사용된다.
        // Appconfig 를 봤을 때 new MemberReposiotry를 호출하는 것처럼 보이나 동일함.


        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("memberServie -> memberRepository :" +memberRepository1);
        System.out.println("orderServie -> memberRepository :" +memberRepository1);
        System.out.println("memberRepository :" +memberRepository);

        assertThat(memberRepository1).isSameAs(memberRepository);
        assertThat(memberRepository2).isSameAs(memberRepository);
    }

    @Test
    void configurataionDeep(){

        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        AppConfig appConfig = ac.getBean(AppConfig.class);

        // 스프링에서 CGLIB라는 바이트 코드 조작 라이브러리를 사용해서 AppConfgig 클래스를 상속 받은 임의의 다른 클래스를 만듦.
        System.out.println("appConfig.getClass() = " + appConfig.getClass());
    }
}