나의 브을로오그으

#5-2. [스프링 핵심 원리-기본편] - @Configuration과 싱글턴 본문

Spring

#5-2. [스프링 핵심 원리-기본편] - @Configuration과 싱글턴

__jhp_+ 2022. 7. 21. 07:58

AppConfig 클래스의 메소드를 보면 싱글턴이 깨질지 안깨질지를 확인해보자.

 

예를 들어 memberService()를 호출하면 MemberServiceImpl() 인스턴스를 반환할 때 memberRepository() 메소드를 호출한다. 여기서 MemoryMemberRepository() 인스턴스를 반환하는데 orderService() 에서도 MemoryMemberRepository() 인스턴스를 반환한다.

그렇다면, memberService()와 orderService() 모두 MemoryMemberRepository() 메소드를 중복 호출하기에 싱글톤 인스턴스를 생성해서 반환하는게 아니라, 각각 개별적인 인스턴스를 반환하는 거 아닐까?

 

package hello.core.singleton;

import hello.core.member.MemberRepository;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.AppConfig;
import hello.core.order.OrderServiceImpl;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ConfigurationSingletonTest {

    @Test
    @DisplayName("스프링 빈이 싱글턴 객체인지 테스트")
    void configurationTest() {
        AnnotationConfigApplicationContext 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 memberRepository_1 = memberService.getMemberRepository();
        MemberRepository memberRepository_2 = orderService.getMemberRepository();

        System.out.println("memberRepository_origin = " + memberRepository);
        System.out.println("memberRepository_1 = " + memberRepository_1);
        System.out.println("memberRepository_2 = " + memberRepository_2);

    }
}

이렇게 확인해보면 memberRepository 인스턴스가 모두 같은 인스턴스로 공유되어 사용되고 있다.

AppConfig 클래스를 보면 분명히 다른 인스턴스를 반환하도록 되어있는데 어떻게 싱글턴 인스턴스로 반환하는 걸까?

 

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

스프링 컨테이너는 싱글톤 레지스트리다. 따라서 스프링 빈이 싱글톤이 되도록 보장해주어야 한다. 그런데 스프링이 자바 코드 자체를 바꿔버리지는 않는다. 그래서 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용한다. 모든 비밀은 @Configuration 을 적용한 AppConfig 에 있다.

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

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

사실 AnnotationConfigApplicationContext 에 파라미터로 넘긴 값스프링 빈으로 등록된다. 그래서 AppConfig 도 스프링 빈이 된다. AppConfig 스프링 빈을 조회해서 클래스 정보를 출력해보자.

 

출력 결과에서 순수한 클래스라면 다음과 같이 출력되어야 한다. class hello.core.AppConfig

 

그런데 예상과는 다르게 클래스 명에 xxxCGLIB가 붙으면서 상당히 복잡해진 것을 볼 수 있다. 이것은 내가 만든 클래스가 아니라 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것이다!

@Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 스프링 빈이 없으면성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어진다. 덕분에 싱글톤이 보장되는 것이다.

 

@Configuration 을 적용하지 않고, @Bean 만 적용하면 어떻게 될까?

@Configuration 을 붙이면 바이트코드를 조작하는 CGLIB 기술을 사용해서 싱글톤을 보장하지만, 만약 @Bean만 적용하면 어떻게 될까?

답) 싱글톤을 보장하지 않는다. 따라서 매번 @Bean 메소드가 호출될 때마다 새로운 인스턴스를 생성해서 반환한다.

 

정리

- @Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다.

- memberRepository() 처럼 의존관계 주입이 필요해서 메서드를 직접 호출할 때 싱글톤을 보장하지 않는다.

- 크게 고민할 것이 없다. 스프링 설정 정보는 항상 @Configuration 을 사용하자.