[Spring MVC #3-2] Context Refresh 과정

mangoo·2023년 10월 17일
0

저번 포스팅에서는 Context refresh 과정 중 4번까지 살펴봤다. 이번 포스팅에서는 나머지 과정이 어떻게 이루어지는지 살펴보도록 하자.

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

        // 1. refresh 준비 단계
        prepareRefresh();

        // 2. BeanFactory 준비 단계
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        prepareBeanFactory(beanFactory);

        try {
            // 3. BeanFactory의 후처리 진행
            postProcessBeanFactory(beanFactory);

            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            
            // 4. BeanFactoryPostProcessor 실행
            invokeBeanFactoryPostProcessors(beanFactory);

            // 5. BeanPostProcessor 등록
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();

            // 6. MessageSource 및 Event Multicaster 초기화
            initMessageSource();
            initApplicationEventMulticaster();

            // 7. onRefresh(웹 서버 생성)
            onRefresh();

            // 8. ApplicationListener 조회 및 등록
            registerListeners();

            // 9. 빈들의 인스턴스화 및 후처리
            finishBeanFactoryInitialization(beanFactory);

            // 10. refresh 마무리 단계
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                    "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
            contextRefresh.end();
        }
    }
}

refresh 메소드 호출을 통해 모든 객체들이 싱글톤으로 인스턴스화되는데, 만약 에러가 발생하면 등록된 빈들을 모두 제거한다. 즉, refresh 단계를 거치면 모든 빈이 인스턴스화 되었거나 어떠한 빈도 존재하지 않거나 둘 중 하나의 상태가 된다. 이러한 refresh 단계는 크게 다음과 같은 순서로 나눌 수 있다.

  1. refresh 준비 단계
  2. BeanFactory 준비 단계
  3. BeanFactory의 후처리 진행
  4. BeanFactoryPostProcessor 실행
  5. BeanPostProcessor 등록
  6. MessageSource 및 Event Multicaster 초기화
  7. onRefresh(웹 서버 생성)
  8. ApplicationListener 조회 및 등록
  9. 빈들의 인스턴스화 및 후처리
  10. refresh 마무리 단계

5. BeanPostProcessor 등록

빈들이 생성되고 나서 빈의 내용이나 빈 자체를 변경하기 위한 빈 후처리기인 BeanPostProcessor를 등록한다. 해당 메서드를 확인해보면 PostProcessingRegistrationDelegation 클래스에 등록 과정을 위임한다.

// AbstractApplicationContext.java
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
	PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}

BeanPostProcessor란?

@Value, @PostConstruct, @Autowired 등이 BeanPostProcessor에 의해 처리되며 이를 위한 구현체들이 등록된다. BeanPostProcessor는 빈이 생성된 후 초기화 메소드가 호출되기 직전과 호출된 직후에 처리 가능한 2가지 메서드를 제공한다.

public interface BeanPostProcessor {
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
    @Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}   

구현체 중 하나인 CommonAnnotationBeanPostProcessor는 @PostConstruct와 @PreDestory를 처리한다.

// PostProcessortRegistrationDelegate.java
public static void registerBeanPostProcessors(
	ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
		
    String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

	// 빈이 생성될 때 INFO 메시지를 로깅하는 BeanPostProcessorChecker를 등록한다. 
	int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
	beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

	// PriorityOrdered, Ordered, 이외 인터페이스를 확장한 BeanPostProcessor를 분리한다. 
	List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
	List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
	List<String> orderedPostProcessorNames = new ArrayList<>();
	List<String> nonOrderedPostProcessorNames = new ArrayList<>();
	for (String ppName : postProcessorNames) {
		if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			priorityOrderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}
		else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
			orderedPostProcessorNames.add(ppName);
		}
		else {
			nonOrderedPostProcessorNames.add(ppName);
		}
	}

	// 먼저 PriorityOrdered를 확장한 BeanPostProcessor를 등록한다.
	sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
	registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

	// Next, register the BeanPostProcessors that implement Ordered.
    // 다음으로, Ordered를 확장한 BeanPostProcessors를 등록한다.
	List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
	for (String ppName : orderedPostProcessorNames) {
		BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
		orderedPostProcessors.add(pp);
		if (pp instanceof MergedBeanDefinitionPostProcessor) {
			internalPostProcessors.add(pp);
		}
	}
	sortPostProcessors(orderedPostProcessors, beanFactory);
	registerBeanPostProcessors(beanFactory, orderedPostProcessors);

    // 일반적인 BeanPostProcessor를 모두 등록한다. 
	List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
	for (String ppName : nonOrderedPostProcessorNames) {
		BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
		nonOrderedPostProcessors.add(pp);
		if (pp instanceof MergedBeanDefinitionPostProcessor) {
			internalPostProcessors.add(pp);
		}
	}
	registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

    // 마지막으로 내부적으로 사용되는 BeanPostProcessor를 다시 등록한다. 
	sortPostProcessors(internalPostProcessors, beanFactory);
	registerBeanPostProcessors(beanFactory, internalPostProcessors);

	// ApplicationListener 인터페이스를 확장한 빈을 탐지하는 post processor를 재등록한다. (processor 체인의 맨 끝으로 옮긴다)
	beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
	}

정리하자면, 의존성 주입은 빈 후처리기에 의해 처리되는 것이며, 실제 빈 대신에 프록시 빈으로 교체되는 작업 역시 빈 후처리기에 의해 처리된다.


6. MessageSource 및 EventMulticaster 초기화

BeanPostProcessor를 등록한 후에는 다국어 처리를 위한 MessageSource와 ApplicationListener에 event를 publish하기 위한 Event Multicaster를 초기화한다.

initMessageSource();
initApplicationEventMulticaster();

뒤에서 다시 나오는 EventMulticaster에 대해서 좀 더 알아보자. 해당 메서드는 ApplicationEventMulticaster가 context에 정의되어 있다면 해당 빈을 클래스 필드에 등록하고, 이외에는 SimpleApplicationEventMulticaster 객체를 생성해 클래스 변수에 할당한다.

protected void initApplicationEventMulticaster() {
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
		this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
		}
	}
	else {
		this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
		beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
		if (logger.isTraceEnabled()) {
			logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " + "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
		}
	}
}

ApplicationEventMulticaster란?

ApplicationListener 객체를 관리하고 이벤트를 발행하는 객체에 의해 확장될 수 있는 인터페이스이다. ApplicationEventPublisher, ApplicationContext는 ApplicationEventMulticaster을 이벤트 발행 위임을 위해 사용한다. 구현체로는 SimpleApplicationEventMulticaster가 있다.


7. onRefresh()

SpringBoot는 기존의 Spring 프레임워크와 달리 내장 웹서버를 탑재하고 있으며, 애플리케이션이 시작될 때 웹서버 객체를 만들어 실행해야 한다. 문제는 애플리케이션 타입에 따라 웹 서버 생성 로직이 달라져야 한다는 것이다. 웹 애플리케이션이 아닌 경우에는 웹 서버가 없을 것이고, 서블릿 웹인 경우에는 톰캣으로, 리액티브 웹인 경우에는 리액터 네티로 만들어야 한다.

[Spring MVC #3] Context Refresh 과정 포스팅에서 onRefresh() 메서드를 다루며 잠깐 소개했던 것처럼 스프링은 템플릿 메서드 패턴을 적용해 문제를 해결한다. onRefresh() 메서드를 살펴보면 내부가 비어 있다. 이는 자식 클래스에서 오버라이딩을 통해 서로 다른 웹 서버를 생성할 수 있도록 한다.

// AbstractApplicationContext.java
protected void onRefresh() throws BeansException {
	// For subclasses: do nothing by default.
}

8. ApplicationListener 조회 및 등록

6. MessageSource 및 EventMulicaster 초기화에서 조회/생성한 EventMuliCaster를 getApplicationEventMulticaster()를 통해 가져온다.

protected void registerListeners() {
	// Register statically specified listeners first.
	for (ApplicationListener<?> listener : getApplicationListeners()) {
		getApplicationEventMulticaster().addApplicationListener(listener);
	}

	// Do not initialize FactoryBeans here: We need to leave all regular beans
	// uninitialized to let post-processors apply to them!
	String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
	for (String listenerBeanName : listenerBeanNames) {
		getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
	}

	// Publish early application events now that we finally have a multicaster...
	Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
	this.earlyApplicationEvents = null;
	if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
		for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
			getApplicationEventMulticaster().multicastEvent(earlyEvent);
		}
	}
}

public Collection<ApplicationListener<?>> getApplicationListeners() {
	return this.applicationListeners;
}

ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
	if (this.applicationEventMulticaster == null) {
		throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
				"call 'refresh' before multicasting events via the context: " + this);
	}
	return this.applicationEventMulticaster;
}

this.applicationListeners의 초기화

해당 클래스 변수의 초기화는 [Spring MVC #2] Context 후처리 작업 및 리프레시를 위한 전처리 작업에서 이루어진다.

public ConfigurableApplicationContext run(String... args) {	
        // [2] Context 후처리 작업 및 리프레시를 위한 전처리 작업
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
}

9. 빈들의 인스턴스화 및 후처리

이제 등록된 Bean Definition을 바탕으로 객체를 생성하는 단계이다. BeanDefinition에는 빈 이름, 스코프 등과 같은 정보가 있어서 이를 바탕으로 객체를 생성한다.

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
	
    // ...
    
	// Allow for caching all bean definition metadata, not expecting further changes.
	beanFactory.freezeConfiguration();

	// Instantiate all remaining (non-lazy-init) singletons.
	beanFactory.preInstantiateSingletons();
}

객체를 생성하기 이전 Bean Definition에 대한 설정이 끝났기 때문에 freeze를 통해 변경되지 않도록 한다.

// DefaultListableBeanFactory.java
@Override
public void freezeConfiguration() {
	clearMetadataCache();
	this.configurationFrozen = true;
	this.frozenBeanDefinitionNames = StringUtils.toStringArray(this.beanDefinitionNames);
}

// 못했

// DefaultListableBeanFactory.java
@Override
	public void preInstantiateSingletons() throws BeansException {
		if (logger.isTraceEnabled()) {
			logger.trace("Pre-instantiating singletons in " + this);
		}

		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
						getBean(beanName);
					}
				}
				else {
					getBean(beanName);
				}
			}
		}

		// Trigger post-initialization callback for all applicable beans...
		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
			if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
				StartupStep smartInitialize = getApplicationStartup().start("spring.beans.smart-initialize")
						.tag("beanName", beanName);
				smartSingleton.afterSingletonsInstantiated();
				smartInitialize.end();
			}
		}
	}

빈 팩토리의 설정과 definition 들을 프리징하고, 남은 싱글톤 빈들을 모두 인스턴스로 등록한다.
RequestMappingHandlerMapping 빈이 초기화 될 때 Handler 가 로드 된다. (RequestMappingHandlerMapping 빈 초기화 과정을 나중에 자세히..)


10. refresh 마무리 단계

모든 빈들을 인스턴스화하였다면 이제 refresh를 마무리하는 단계이다. 여기서는 애플리케이션 컨텍스트를 준비하기 위해 사용되었던 resourceCache를 제거하고, Lifecycle Processor를 초기화하여 refresh를 전파하고, 최종 이벤트를 전파하며 마무리된다. Lifecycle Processor에는 웹서버와 관련된 부분이 있어서 refresh가 전파되면, 웹서버가 실행된다.

protected void finishRefresh() {
	// Clear context-level resource caches (such as ASM metadata from scanning).
	clearResourceCaches();

	// Initialize lifecycle processor for this context.
	initLifecycleProcessor();

	// Propagate refresh to lifecycle processor first.
	getLifecycleProcessor().onRefresh();

	// Publish the final event.
	publishEvent(new ContextRefreshedEvent(this));
}

0개의 댓글