Notice
Recent Posts
Recent Comments
Link
«   2025/09   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

Miscellaneous

Spring의 @Configuration이 싱글톤을 보장하는 방법 본문

Develop/Spring

Spring의 @Configuration이 싱글톤을 보장하는 방법

5-ms 2025. 8. 18. 21:00

서론

취준 스터디에서 싱글톤과 프록시에 대해 공부하던 중, @Configuration 어노테이션이 빈을 관리하는 방법에 대해 조사하게 되었습니다.

조사 과정에서 @Configuration의 내부 동작 방식을 깊이 살펴보았고, 빈이 관리되는 과정을 추적하였습니다. 

해당 포스트에서는 그 과정을 정리하여, @Configuration이 어떻게 빈을 싱글톤으로 보장하는지 원리를 살펴보겠습니다.

 


 

 

본론

@Configuration
public class AppConfig {
    @Bean
    public A a() {
        return new A(b());
    }

    @Bean
    public B b() {
        return new B();
    }
}

위 코드는 간단한 Configuration 클래스입니다.

위 코드에서 @Configuration이나 @Bean 어노테이션이 없다면, 총 2개의 B 인스턴스가 생성될 것입니다. 

하지만, 어노테이션 덕분에, 우리는 B가 단 한번만 생성된다는 것을 보장할 수 있습니다.

 

그럼 @Configuration은 어떻게 빈을 싱글톤으로 관리할까요?

스프링은 이를 프록시를 이용하여 구현했습니다.

스프링은 @Configuration이 붙은 클래스에 대해서 내부적으로 새로운 클래스를 생성합니다.

다시 말해, 위의 AppConfig를 바탕으로 아래의 새로운 클래스가 생성되어 AppConfig 대신 빈을 관리합니다.

class AppConfig$$EnhancerBySpringCGLIB extends AppConfig {
    private BeanFactory beanFactory;

    @Override
    public A a() {
        if (beanFactory.containsSingleton("a")) { //이미 존재하는 빈인지 확인
            return (A) beanFactory.getSingleton("a"); //존재한다면 해당 빈 return 
        }
        A a = super.a(); // 존재하지 않는다면 빈 생성 및 저장
        beanFactory.registerSingleton("a", a);
        return a;
    }

    @Override
    public B b() {
		// A와 동일 ...
    }
}

(실제 코드가 이렇다는 것은 아니고, 대략적인 로직이 위와 같습니다!)

@Configuration이 붙은 클래스를 바탕으로 위와 같은 새로운 클래스가 생성되고, @Bean이 붙은 메서드에 대해서 위와 같은 메서드가 구현됩니다. 

위 코드에서 주의 깊게 보아야 하는 부분은 총 2가지 입니다.

 

1. 클래스명 맨 뒤 CGLIB

2. beanFactory.containsSingleton()

 

1. CGLIB

생성된 클래스명을 보면, CGLIB라는 단어를 확인하실 수 있습니다.

CGLIB는 스프링이 프록시를 생성하는 방법 중 하나입니다.  

 

Spring이 프록시를 생성하는 방법에는 두가지가 존재합니다.

클래스 기반의 프록시는 CGLIB를, 인터페이스 기반 프록시는 JDK Dynamic Proxy를 사용합니다.

 

예상하셨다시피, @Configuration 클래스는 CGLIB를 이용하여 프록시가 생성됩니다.

@Configuration은 대부분 구체 클래스에 정의되고, @Bean 메서드 또한 클래스 안에서 정의되기 때문입니다. 

 

CGLIB에 의해서 구현체 클래스를 바탕으로 이를 상속받아 프록시가 생성됩니다.

따라서 CGLIB에 의해 생성된 클래스는 AppConfig를 상속받고 있으며, 해당 클래스에서 @Override된 메서드에서는 동일한 시그니쳐의 부모 메서드를 호출합니다. (super.a())

 

 

※ CGLIB는 런타임 동안 바이트코드를 조작하여 AppConfig를 상속한 새로운 클래스를 메모리에 생성합니다. 따라서 생성된 클래스는 .java 파일로 생성되는 것은 아닙니다!

 

 

2. beanFactory.containsSingleton()

BeanFactory는 빈을 생성 및 관리하는 최상위 인터페이스입니다.

Spring의 빈 컨테이너 계층 구조에서 DefaultSingletonBeanRegistry가 실제 싱글톤 빈 관리를 담당합니다. 그리고 해당 클래스 내부에는 아래와 같은 Map이 존재합니다.

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

 

해당 Map의 key로는 빈의 이름을, value는 빈 인스턴스를 관리합니다.

위 클래스의 containsSingleton()과 getSinleton() 메서드는 SingletonBeanRegistry 인터페이스에서 정의되며, DefaultSingletonBeanRegistry에서 실제 구현됩니다.

 


결론

결론적으로,

@Configuration 어노테이션은 CGLIB 프록시를 통해 빈의 싱글톤을 보장합니다.

Spring은 런타임에 원본 Configuration 클래스를 상속한 새로운 프록시 클래스를 생성하고, 각 @Bean 메서드를 Override하여 싱글톤 관리 로직을 삽입합니다.

이를 통해 개발자가 메서드를 여러 번 호출하더라도 동일한 빈 인스턴스가 반환되는 것입니다.

 

추가로,

@Configuration가 아닌, @Component로 빈을 생성하는 경우에는 CGLIB 프록시를 만들지 않습니다.

따라서 해당 클래스 내부에서 @Bean을 사용해도 싱글톤이 보장되지 않습니다.

또, @Configuration(proxyBeanMethods = false)로 선언하는 경우에도 프록시를 생성하지 않아, 싱글톤이 깨지게 됩니다. 

 

 

이 글이 @Configuration의 동작 원리를 이해하는 데 도움이 되셨기를 바랍니다.

감사합니다.