버전은 스프링 부트 3.0.8이며, Spring MVC를 사용했습니다.
@SpringBootApplication이 명시된 클래스는 위와 같습니다.
이전 글에서 createApplicationContext(), setApplicationStartup()을 살펴봤습니다.
이번 글에서는 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);
}
해당 메서드의 전체 코드입니다.
하나씩 살펴보도록 하겠습니다.
ApplicationContext에 이전에 설정한 Environment를 설정합니다.
ApplicationContext와 관련된 후처리기를 설정합니다.
BeanNameGenerator, ResourceLoader, ConversionService를 설정할 수 있습니다.
현재 상태에서는 ApplicationContext가 단순 생성되어있는 시점이기 때문에 ConversionService, 타입 변환기만을 추가합니다.
이 때 Environment에서 사용했었던 타입 변환기를 재사용합니다.
AOT 옵티마이저를 사용한다면 이와 관련된 ApplicationContextLintializer를 등록합니다.
AOT 옵티마이저를 사용하지 않으므로 동작하지 않습니다.
SpringApplication에 지정된 모든 ApplicationContextInitializer에 생성한 ApplicationContext를 세팅합니다.
getIntializers()를 통해 AutoConfiguratioin으로 등록된 ApplicationContextIntializer에 초기화 중인 ApplicationContext를 등록합니다.
각 Initializer는 다음과 같은 동작을 수행합니다.
이 Initializer는 boot의 spring.factories와 autoconfiguration의 spring.factories에서 확인할 수 있습니다.
ApplicationEvent를 발행하고, 대기하고 있던 모든 Listener에게 모두 이벤트를 발행(= multicast) 합니다.
SpringApplicationRunListener.contextPrepared()를 호출합니다.
해당 메서드는 모든 Listener에게 contextPrepared() 메서드를 호출합니다.
다만 현 시점에서 등록된 Listener는 EventPublishingRunListener 밖에 존재하지 않습니다.
EventPublishingRunListener.contextPrepared()는 ApplicationEvent의 일종인 ApplicationStartingEvent를 발행합니다.
현 시점에서 EventPublishingRunListener에 세팅된 Listener은 LoggingApplicationListener 하나뿐이므로 해당 Listener가 동작합니다.
SpringApplication의 클래스 로더를 조회해 loggingSystem을 세팅하고, 초기화 전 전처리 작업을 수행합니다.
Logback을 사용하고 있기 때문에 LogbackLoggingSystem이 세팅됩니다.
부트스트랩의 동작을 종료시키는 ApplicationEvent를 발행합니다.
DefaultBootstrapContext.close()가 호출되며, 이 때 BootstrapContextClosedEvent가 발행됩니다.
다만 DefaultBootstrapContext에 등록된 ApplicationEventMulticaster에는 아무런 Listener가 등록되어 있지 않기 때문에 별도로 동작하지 않습니다.
다음과 같이 실행한 스프링 부트 애플리케이션과 관련된 로그를 출력합니다.
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"
스프링 부트 애플리케이션 실행 시 옵션으로 부여한 ApplicationArguments와 SpringBootBanner를 등록합니다.
순환참조 및 동일한 빈 이름에 대한 오버라이딩 가능 여부를 세팅합니다.
이후 빈에 대한 지연 초기화가 가능한 경우 이를 처리할 BeanFactoryPostProcessor를 등록합니다.
기본 값은 지연 초기화가 비활성화이고, 별도의 설정을 하지 않아 동작하지 않습니다.
우선순위를 고려해 환경 설정을 관리할 수 있는 BeanFactoryPostProcessor를 등록합니다.
기준이 되는 sources를 가져옵니다.
별도의 설정을 하지 않았다면 @SpringBootApplication이 명시된 클래스만이 반환됩니다.
이후 AnnotatedBeanDefinitionReader.register()가 호출됩니다.
AnnotatedBeanDefinitionReader.register() 내부에서 BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry)를 호출합니다.
AnnotationConfigServletWebServerApplicationContext.registerBeanDifinition()을 통해 @SpringBootApplication의 BeanDefinition이 등록됩니다.
별도의 별칭을 지정하지 않았으므로, 이후 별도의 동작은 없습니다.
@SpringBootApplication가 명시된 클래스 DdangApplication이 빈으로 등록되는 것을 확인할 수 있습니다.
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는 ApplicationPreparedEvent나 ApplicationFailedEvent나 동일하게 동작하며, 발생한 이벤트에 따른 로그를 출력합니다.
LoggingApplicationListener의 경우 LOGGING_SYSTEM_BEAN_NAME, LOGGER_GROUPS_BEAN_NAME, LOGGING_LIFECYCLE_BEAN_NAME를 빈으로 등록합니다.
ApplicationContext를 refresh 하기 전, 다음과 같이 필요한 준비를 모두 수행합니다.