caffeine-library / pro-spring-5

🌱 전문가를 위한 스프링5를 읽는 스터디
5 stars 0 forks source link

[question] 싱글턴 레지스트리의 동작 원리 #45

Closed wooyounggggg closed 3 years ago

wooyounggggg commented 3 years ago

질문

싱글턴 레지스트리의 동작 원리

상세 내용

자바에서 싱글턴 객체를 만들기 위해서는 굉장히 많은 비용을 감수해야 하는 것으로 알고 있습니다. - 참고

그래서 스프링에서는 싱글턴 레지스트리를 통해 자바 빈으로 등록된 객체를 개발자가 따로 처리할 필요 없이 싱글턴으로 등록해주는데, 싱글턴 레지스트리가 어떠한 방식으로 작동하기에 쉽게 싱글턴을 만들 수 있게 하는 것인지 궁금합니다!

연관 챕터

20

참고

wooyounggggg commented 3 years ago

단어의 혼동이 있었네요.

ApplicationContext 내부에 싱글턴 레지스트리가 따로 존재해서 그것을 관리하는 것이 ApplicationContext라고 이해하고 있었는데, 조금 조사를 해보니 ApplicationContext가 싱글턴 레지스트리로써의 역할을 한다고 합니다.

추가적으로 ApplicationContext가 빈에 설정된 Scope을 검사하여 빈을 해당 Scope으로 선언해주기 위한 과정을 조사하였습니다.

/* AnnotationConfigApplicationContext.java */
public class AnnotationConfigApplicationContext 
    extends GenericApplicationContext 
    implements AnnotationConfigRegistry {

   ...
   public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
        ...
        this.register(componentClasses); // 1. 해당 메서드 정의로 이동(in this)
        this.refresh(); // 6. ApplicationContext에 빈 등록
    }

    public void register(Class<?>... componentClasses) {
        Assert.notEmpty(componentClasses, "At least one component class must be specified");
        StartupStep registerComponentClass = this.getApplicationStartup().start("spring.context.component-classes.register").tag("classes", () -> {
            return Arrays.toString(componentClasses);
        });
        this.reader.register(componentClasses); // 2. 해당 메서드 정의로 이동(in AnnotatedBeanDefinitionReader.java)
        registerComponentClass.end();
    }
}
/* AnnotatedBeanDefinitionReader.java */
public class AnnotatedBeanDefinitionReader {
    ...
    public void register(Class<?>... componentClasses) {
        Class[] var2 = componentClasses;
        int var3 = componentClasses.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            Class<?> componentClass = var2[var4];
            this.registerBean(componentClass); // 3. 해당 메서드 정의로 이동(in this)
        }

    }

    public void registerBean(Class<?> beanClass) {
        this.doRegisterBean(beanClass, (String)null, (Class[])null, (Supplier)null, (BeanDefinitionCustomizer[])null); // 4. 해당 메서드 정의로 이동(in this)
    }

    private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) {
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
        if (!this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
                abd.setInstanceSupplier(supplier);
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
                abd.setScope(scopeMetadata.getScopeName()); // 5. ScopeMetadata에 설정된 scope name을 AnnotatedBeanDefinition에 설정
                ...
        }
    }
}

1. AnnotationConfigApplicationContext 생성자 호출( this.register(componentClasses) )

ApplicationContext를 구성하면서, Context를 refresh하기 이전에, Config 클래스들을 전달받아 register 메서드의 파라미터로 전달합니다.

2. AnnotatedBeanDefinitionReader의 register로 Config 목록 전달

AnnotationConfigApplicationContext의 register 메서드 내부에서 일련의 전처리를 거친 이후, 전달된 Config들을 AnnotatedBeanDefinitionReader로 전달하여 BeanDefinition 정보를 읽고, 해당 정보들을 AnnotatedBeanDefinitionReader의 컨텍스트에 등록하도록 위임합니다.

3. 전달된 Config들을 iterating하며 각자 registerBean 메서드 파라미터로 전달

AnnotatedBeanDefinitionReader는 AnnotationConfigApplicationContext로부터 전달된 Config를 하나씩 참조하며, 참조된 Config 각각을 registerBean 메서드의 파라미터로 전달합니다.

4. doRegisterBean 호출

registerBean은 Config를 전달받아, doRegisterBean을 호출하기 위한 파라미터 규격을 형성하여 doRegisterBean을 호출합니다.

5. Scope Metadata를 구성하여 Scope 설정

doRegisterBean은 AnnotatedGenericBeanDefinition의 생성자를 호출하면서, 전달된 Config를 파라미터로 설정합니다. 이로써, 우리가 구성한 Config.java 파일은 BeanDefinition으로 Converting 됩니다.

이후, Converting 된 BeanDeifinition을 ScopeMetadataResolver에게 전달하여 ScopeMetadata를 받아오고, 해당 ScopeMetadata를 사용해 BeanDefinition 내부에 scope data를 등록합니다.

6. ApplicationContext의 refresh() 메서드를 활용해 빈 생성

모든 메타데이터의 설정이 끝난 이후, refresh() 메서드에서 사전에 설정된 다양한 Config 정보를 바탕으로 빈들을 생성해 ApplicationContext 내부에 등록합니다. 이 때, 이전에 설정해둔 Scope를 참조하여 빈의 Scope를 결정합니다.

Scope 설정 전

Scope 설정 전

Scope 설정 후

Scope 설정 후