ApplicationContext Refresh - prepareContext()

appti·2024년 3월 13일
0

분석

목록 보기
13/23

환경

버전은 스프링 부트 3.0.8이며, Spring MVC를 사용했습니다.

@SpringBootApplication이 명시된 클래스는 위와 같습니다.

서론

이전 글에서 createApplicationContext(), setApplicationStartup()을 살펴봤습니다.

이번 글에서는 prepareContext()를 살펴보겠습니다.

SpringApplication.prepareContext()

ApplicationContext와 관련된 설정을 하기 전 필요한 설정을 진행하는 메서드입니다.

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
	context.setEnvironment(environment);
	postProcessApplicationContext(context);
	addAotGeneratedInitializerIfNecessary(this.initializers);
	applyInitializers(context);
	listeners.contextPrepared(context);
	bootstrapContext.close(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {
		autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);
		if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {
			listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
	}
	if (this.lazyInitialization) {
		context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
	}
	context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
	if (!AotDetector.useGeneratedArtifacts()) {
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
	}
	listeners.contextLoaded(context);
}

해당 메서드의 전체 코드입니다.
하나씩 살펴보도록 하겠습니다.

context.setEnvironment()

ApplicationContext에 이전에 설정한 Environment를 설정합니다.

postProcessApplicationContext()

ApplicationContext와 관련된 후처리기를 설정합니다.

BeanNameGenerator, ResourceLoader, ConversionService를 설정할 수 있습니다.
현재 상태에서는 ApplicationContext가 단순 생성되어있는 시점이기 때문에 ConversionService, 타입 변환기만을 추가합니다.

이 때 Environment에서 사용했었던 타입 변환기를 재사용합니다.

addAotGeneratedInitializerIfNecessary()

AOT 옵티마이저를 사용한다면 이와 관련된 ApplicationContextLintializer를 등록합니다.

AOT 옵티마이저를 사용하지 않으므로 동작하지 않습니다.

applyInitializers()

SpringApplication에 지정된 모든 ApplicationContextInitializer에 생성한 ApplicationContext를 세팅합니다.

getIntializers()를 통해 AutoConfiguratioin으로 등록된 ApplicationContextIntializer에 초기화 중인 ApplicationContext를 등록합니다.

각 Initializer는 다음과 같은 동작을 수행합니다.

  • SharedMetadataReaderFactoryContextInitializer
    • 메타데이터 리더 팩토리의 공유 인스턴스를 관리합니다.
    • 메타데이터 리더의 재사용성과 성능을 향상시킵니다.
  • DelegatingApplicationContextInitializer
    • 등록한 사용자 정의 Initializer의 작업을 위임받아 수행합니다.
  • ContextIdApplicationContextInitializer
    • ApplicationContext의 ID를 설정합니다.
    • 기본 값은 application이며, spring.application.name으로 값을 지정할 수 있습니다.
  • ConditionEvaluationReportLoggingListener
    • 빈 등록 시 @Condition에 의한 결과(빈이 등록되는지 여부, 충돌 등)를 로깅합니다.
    • DEBUG 레벨에서 로깅됩니다.
  • ConfigurationWarningsApplicationContextInitializer
    • 잘못된 구성(Warning Configuration)에 대한 경고를 표시합니다.
  • RSocketPortInfoApplicationContextInitializer
    • RSocketServer 서버가 수신 대기 중인 포트에 대한 정보 설정합니다.
  • ServerPortInfoApplicationContextInitializer
    • WebServer 서버가 수신 대기 중인 포트에 대한 정보를 설정합니다.

이 Initializer는 boot의 spring.factories와 autoconfiguration의 spring.factories에서 확인할 수 있습니다.

listeners.contextPrepared()

ApplicationEvent를 발행하고, 대기하고 있던 모든 Listener에게 모두 이벤트를 발행(= multicast) 합니다.

SpringApplicationRunListener.contextPrepared()를 호출합니다.

해당 메서드는 모든 Listener에게 contextPrepared() 메서드를 호출합니다.

다만 현 시점에서 등록된 Listener는 EventPublishingRunListener 밖에 존재하지 않습니다.

EventPublishingRunListener.contextPrepared()는 ApplicationEvent의 일종인 ApplicationStartingEvent를 발행합니다.

현 시점에서 EventPublishingRunListener에 세팅된 Listener은 LoggingApplicationListener 하나뿐이므로 해당 Listener가 동작합니다.

SpringApplication의 클래스 로더를 조회해 loggingSystem을 세팅하고, 초기화 전 전처리 작업을 수행합니다.

Logback을 사용하고 있기 때문에 LogbackLoggingSystem이 세팅됩니다.

bootstrapContext.close()

부트스트랩의 동작을 종료시키는 ApplicationEvent를 발행합니다.

DefaultBootstrapContext.close()가 호출되며, 이 때 BootstrapContextClosedEvent가 발행됩니다.

다만 DefaultBootstrapContext에 등록된 ApplicationEventMulticaster에는 아무런 Listener가 등록되어 있지 않기 때문에 별도로 동작하지 않습니다.

Startup 로고 출력

다음과 같이 실행한 스프링 부트 애플리케이션과 관련된 로그를 출력합니다.

2024-03-11T19:58:43.364+09:00  INFO 3334 --- [           main] com.ddang.ddang.DdangApplication         : Starting DdangApplication using Java 17.0.2 with PID 3334 ()
2024-03-11T19:58:44.354+09:00  INFO 3334 --- [           main] com.ddang.ddang.DdangApplication         : No active profile set, falling back to 2 default profiles: "local", "console-logging"

specific singleton bean 등록

순환 참조, 빈 오버라이딩, 빈 지연 초기화 관련 세팅

스프링 부트 애플리케이션 실행 시 옵션으로 부여한 ApplicationArguments와 SpringBootBanner를 등록합니다.

순환참조 및 동일한 빈 이름에 대한 오버라이딩 가능 여부를 세팅합니다.

이후 빈에 대한 지연 초기화가 가능한 경우 이를 처리할 BeanFactoryPostProcessor를 등록합니다.

기본 값은 지연 초기화가 비활성화이고, 별도의 설정을 하지 않아 동작하지 않습니다.

우선순위를 고려해 환경 설정을 관리할 수 있는 BeanFactoryPostProcessor를 등록합니다.

@SpringBootApplication이 명시된 클래스 빈 등록

기준이 되는 sources를 가져옵니다.

별도의 설정을 하지 않았다면 @SpringBootApplication이 명시된 클래스만이 반환됩니다.

이후 AnnotatedBeanDefinitionReader.register()가 호출됩니다.

AnnotatedBeanDefinitionReader.register() 내부에서 BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry)를 호출합니다.

AnnotationConfigServletWebServerApplicationContext.registerBeanDifinition()을 통해 @SpringBootApplication의 BeanDefinition이 등록됩니다.

별도의 별칭을 지정하지 않았으므로, 이후 별도의 동작은 없습니다.

@SpringBootApplication가 명시된 클래스 DdangApplication이 빈으로 등록되는 것을 확인할 수 있습니다.

listeners.contextLoaded()

ApplicationContext refresh 준비가 끝났음을 알리는 이벤트를 발행합니다.

SpringApplicationRunListeners.contextLoaded()가 동작합니다.

listeners.contextPrepared()와 마찬가지로 단 하나 등록되어있는 EventPublishingRunListener.contextLoaded()를 호출합니다.

ParentContextCloserApplicationListener를 등록합니다.

해당 Listener는 ContextClosedEvent를 감지해, 현재 ApplicationContext의 자식 ApplicationContext를 close하는 역할을 수행합니다.

8개의 Listener를 ApplicationContext에 세팅합니다.

발행하는 시점은 prepareContext()의 마지막 코드, 즉 ApplicationContext의 refresh 준비가 끝났음을 의미합니다.

그러므로 이를 알려주기 위한 이벤트인 ApplicationPreparedEvent를 발행합니다.

이 때 해당 이벤트를 감지하는 Listener는 for문에서 등록한 8개의 Listener이며, 다음과 같은 Listener가 실제 ApplicationPreparedEvent에 대한 팩토리 훅이 동작됩니다.

  • EnvironmentPostProcessorApplicationListener
  • LoggingApplicationListener

EnvironmentPostProcessorApplicationListener는 ApplicationPreparedEvent나 ApplicationFailedEvent나 동일하게 동작하며, 발생한 이벤트에 따른 로그를 출력합니다.

LoggingApplicationListener의 경우 LOGGING_SYSTEM_BEAN_NAME, LOGGER_GROUPS_BEAN_NAME, LOGGING_LIFECYCLE_BEAN_NAME를 빈으로 등록합니다.

정리

ApplicationContext를 refresh 하기 전, 다음과 같이 필요한 준비를 모두 수행합니다.

  1. ApplicationContext에 Environment를 초기화합니다.
  2. ApplicationContextInitializer의 로직을 수행하기 위해 ApplicationContext를 세팅합니다.
  3. SpringArguments, printBanner를 빈으로 등록합니다.
  4. 순환 참조, 빈 오버라이딩, 빈 지연 초기화에 대한 설정을 진행합니다.
  5. 우선 순위를 고려해 환경 설정을 관리하는 PropertySourceOrderingBeanFactoryPostProcessor를 빈으로 등록합니다.
  6. @SpringBootApplication이 명시된 클래스의 BeanDefinition을 등록합니다.
  7. refresh를 위한 준비가 끝났다는 이벤트를 발행합니다. 이 때, 이벤트에 맞춰 다양한 Listener들이 필요한 작업을 수행합니다.
profile
안녕하세요

0개의 댓글