Spring/스프링 핵심 원리 - 기본편

[싱글톤 컨테이너] @Configuration - 싱글톤 보장

alsruds 2023. 12. 1. 22:18
🧐강의🧐
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

 

🫧 궁금증

· AppConfig 에서 MemoryMemberRepository 가 2 번 호출되면서 싱글톤이 깨지지는 않을까 ??

@Configuration
public class AppConfig {

    // @Bean memberService -> new MemoryMemberRepository()
    // @Bean orderService -> new MemoryMemberRepository()

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    ...
}

 

TEST

☑️ MemoryMemberRepository 를 호출하는 MemberServiceImpl 과 OrderServiceImpl 의 객체 확인하기

 

1. MemberServiceImpl 에 테스트 코드 추가

public class MemberServiceImpl implements MemberService{

    ...

    // 테스트 용도
    public MemberRepository getMemberRepository() {
        return memberRepository;
    }
}

 

2. OrderServiceImpl 에 테스트 코드 추가

public class OrderServiceImpl implements OrderService{

    ...

    // 테스트 용도
    public MemberRepository getMemberRepository() {
        return memberRepository;
    }
}

 

3. test/java/hello.core/singleton/ConfigurationSingletonTest Class 생성

public class ConfigurationSingletonTest {

    @Test
    void configurationTest() {
        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);

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

같은 인스턴스 조회

 

AppConfig 에서 호출 로그 확인하기

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        // soutm
        System.out.println("call AppConfig.memberService");
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemoryMemberRepository 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());
    }

    ...
}

3번의 call 발생 : memberService, memberRepository, orderService

 

☑️ 예상 call 호출 (순서 보장X)

→ call AppConfig.memberService

call AppConfig.memberRepository

call AppConfig.memberRepository

call AppConfig.orderService

call AppConfig.memberRepository

 

✅ 실제 call 호출

call AppConfig.memberService

call AppConfig.memberRepository

call AppConfig.orderService

 

🫧 궁금증 해결

· 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용 → CGLIB

· 비밀은 @Configuration 을 적용한 AppConfig 에 .. !!

AppConfig@CGLIB : 임의의 다른 클래스가 싱글톤이 보장되도록 도와줌

 

TEST

☑️ 내가 만든 클래스가 아닌, CGLIB 라이브러리를 이용한 임의의 다른 클래스가 스프링 빈 등록한 것 확인하기

public class ConfigurationSingletonTest {

    ...

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

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

 

➡️ 스프링이 CGLIB(바이트코드 조작 라이브러리)를 이용해 AppConfig 를 상속받은 임의의 다른 클래스 생성 후 스프링 빈으로 등록

 

· 예상 코드

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

 

@Configuration 삭제 시

☑️ @Configuration 을 적용하지 않고, @Bean 만 적용 → AppConfig 에서 @Configuration 삭제

// @Configuration
public class AppConfig { ... }

AppConfig 가 CGLIB 라이브러리 없이 순수한 AppConfig 로 스프링 빈에 등록됨

 

➡️ 각자 다른 MemoryMemberRepository 인스턴스를 가지고 있음

 

정리
- @Bean 만 사용해도 스프링 빈 등록 가능 (싱글톤 보장X)
- 항상 @Configuration 사용하기 !!