@SpringBootTest 의 동작 원리를 확인합니다ApplicationContext 초기화 과정에 대해 살펴봅니다.DelegatingSmartContextLoader 는 세부 AbstractGenericContextLoader 에 ApplicationContext 생성을 위임합니다.AnnotationConfigContextLoader : 설정 클래스에서 ApplicationContext 를 로딩합니다.GenericXmlContextLoader : XML 에서 ApplicationContext 를 로딩합니다.GenericGroovyXmlContextLoader : Groovy 와 XML 에서 ApplicationContext 를 로딩합니다.private GenericApplicationContext loadContext(
MergedContextConfiguration mergedConfig, boolean forAotProcessing) throws Exception {
validateMergedContextConfiguration(mergedConfig); // do nothing
// 1. GenericApplicationContext 를 생성합니다.
GenericApplicationContext context = createContext();
try {
ApplicationContext parent = mergedConfig.getParentApplicationContext();
if (parent != null) {
context.setParent(parent);
}
prepareContext(context); // do nothing
// 2. ApplicationContext 를 설정합니다.
prepareContext(context, mergedConfig);
customizeBeanFactory(context.getDefaultListableBeanFactory()); // do nothing
// 3. BeanDefinition 을 로드합니다.
loadBeanDefinitions(context, mergedConfig);
// 4. 기본적으로 필요한 빈을 등록합니다.
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
customizeContext(context); // do nothing
// 5. ContextCustomizer 를 실행합니다.
customizeContext(context, mergedConfig);
if (!forAotProcessing) {
// 6. ApplicationContext 를 초기화 합니다.
context.refresh();
context.registerShutdownHook();
}
return context;
} catch (Exception ex) {
throw new ContextLoadException(context, ex);
}
}
ApplicationContext 가 생성되는 순서는 다음과 같습니다.
GenericApplicationContext 를 생성BeanDefinition 이 등록되기전에 ApplicationContext 를 설정합니다.ActiveProfile 을 등록합니다.@TestPropertySource(locations))@TestPropertySource(properties))ApplicationContextInitializer 를 이용해 ApplicationContext 를 초기화 합니다.BeanDefinition 을 로드합니다.AnnotationAwareOrderComparatorContextAnnotationAutowireCandidateResolverConfigurationClassPostProcessorAutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessorPersistenceAnnotationBeanPostProcessorEventListenerMethodProcessorDefaultEventListenerFactoryContextCustomizer 를 실행합니다.ApplicationContext 를 초기화 합니다.SpringBootContextLoader 는 SpringApplication 을 실행하고 생성된 ApplicationContext 를 반환합니다.private ApplicationContext loadContext(MergedContextConfiguration mergedConfig, Mode mode,
ApplicationContextInitializer<ConfigurableApplicationContext> initializer) throws Exception {
// 1. @SpringBootTest 어노테이션 추출
assertHasClassesOrLocations(mergedConfig);
SpringBootTestAnnotation annotation = SpringBootTestAnnotation.get(mergedConfig);
String[] args = annotation.getArgs(); // SpringBootTest
// 2. SpringApplication 실행
// 2-1. SpringApplication 의 main 메서드 실행을 통해 애플리케이션 실행
UseMainMethod useMainMethod = annotation.getUseMainMethod();
Method mainMethod = getMainMethod(mergedConfig, useMainMethod);
if (mainMethod != null) {
ContextLoaderHook hook = new ContextLoaderHook(mode, initializer,
(application) -> configure(mergedConfig, application));
return hook.runMain(() -> ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { args }));
}
// 2-2. SpringApplication#run 을 통해 애플리케이션 실행
SpringApplication application = getSpringApplication();
configure(mergedConfig, application);
ContextLoaderHook hook = new ContextLoaderHook(mode, initializer, ALREADY_CONFIGURED);
return hook.run(() -> application.run(args));
}
// 3. 설정
private void configure(MergedContextConfiguration mergedConfig, SpringApplication application) {
// 3-1. Context 설정 등록
application.setMainApplicationClass(mergedConfig.getTestClass());
application.addPrimarySources(Arrays.asList(mergedConfig.getClasses()));
application.getSources().addAll(Arrays.asList(mergedConfig.getLocations()));
// 3-2. ApplicationContextInitializer 추출
List<ApplicationContextInitializer<?>> initializers = getInitializers(mergedConfig, application);
// 3-3. Web 환경별 설정
if (mergedConfig instanceof WebMergedContextConfiguration) {
application.setWebApplicationType(WebApplicationType.SERVLET);
if (!isEmbeddedWebEnvironment(mergedConfig)) {
new WebConfigurer().configure(mergedConfig, initializers);
}
} else if (mergedConfig instanceof ReactiveWebMergedContextConfiguration) {
application.setWebApplicationType(WebApplicationType.REACTIVE);
} else {
application.setWebApplicationType(WebApplicationType.NONE);
}
// 3-4. ApplicationContextFactory 설정
application.setApplicationContextFactory(getApplicationContextFactory(mergedConfig));
if (mergedConfig.getParent() != null) {
application.setBannerMode(Banner.Mode.OFF);
}
// 3-5. ApplicationContextInitializer 등록
application.setInitializers(initializers);
// 3-6. ConfigurableEnvironment 등록
ConfigurableEnvironment environment = getEnvironment();
if (environment != null) {
prepareEnvironment(mergedConfig, application, environment, false);
application.setEnvironment(environment);
} else {
application.addListeners(new PrepareEnvironmentListener(mergedConfig));
}
}
ApplicationContext 가 생성되는 순서는 다음과 같습니다.
@SpringBootTest 어노테이션 추출@SpringBootTest(useMainMethod) 가 ALWAYS 혹은 WHEN_AVAILABLE 일때 실행됩니다.SpringApplication#run 을 통해 애플리케이션 실행@SpringBootTest(useMainMethod) 가 NEVER 혹은 WHEN_AVAILABLE 일때 실행됩니다.SpringApplication 을 설정합니다.ApplicationContextInitializer 추출ContextCustomizer 는 ContextCustomizerAdapter 를 통해 ApplicationContextInitializer 로 등록됩니다.ApplicationContextFactory 설정ApplicationContext 를 생성하는 팩토리를 등록합니다.ApplicationContextInitializer 등록ConfigurableEnvironment 등록ApplicationContext 초기화 과정에 대해 살펴봅니다.ContextCustomizer)