스프링부트 실행 과정
1. SpringApplication 에서 static method run 실행
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
Java
복사
primarySources 에서 SpringApplication 생성자를 호출하며 아래 과정을 거쳐 mainApplicationClass 로 시작점 어플리케이션이 등록됩니다.
public static void main(String[] args) {
SpringApplication.run(PlaygroundApplication.class, args);
}
Java
복사
위와 같은 방식으로 SpringApplication을 실행했다고 가정하면 PlaygroundApplication.class 가 primarySource 로 등록되는 것.
2. ConfiguableApplicationContext 를 최종적으로 반환하는 method run 실행
public ConfigurableApplicationContext run(String... args) {
...
}
Java
복사
이후 run 에서 어떤 작업이 수행되는지 살펴보겠습니다.
3. 시간 측정
Startup startup = Startup.create();
Java
복사
애플리케이션의 시작 시간, 실행 시간 등 실행 시간에 관련된 메서드를 반환합니다.
전체 실행 과정에서 그렇게 중요한 부분까지는 아니어서 이 글에서는 자세한 탐구는 생략합니다.
4. 애플리케이션 종료 시에 동작할 훅 등록
if (this.registerShutdownHook) {
SpringApplication.shutdownHook.enableShutdownHookAddition();
}
Java
복사
스프링 어플리케이션이 종료될 때 추가적인 작업을 수행하기 위한 훅을 등록합니다.
자원 해제, 로그 기록, 데이터 정리 등 여러가지 추가 작업을 수행하게 되는데, 자세한 내용은 다른 글에서 다뤄 보겠습니다.
5. BootStrapContext 생성 및 ApplicationContext 를 생성하기 위한 사전작업
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
Java
복사
각 한 줄 마다 설명하면 다음과 같습니다.
1.
BootStrapContext 는 ApplicationContext 가 생성되기 전에 스프링부트 어플리케이션 실행에 필요한 각종 환경 관리(Enviroment 객체 등)를 담당하는 객체입니다. 또한 싱글톤 리소스에 대한 지연 접근(lazy)을 지원합니다.
또한 ConfigurableApplicationContext 객체를 null로 생성합니다.
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
Java
복사
2.
java.awt.healdess 프로퍼티를 찾아 적용합니다. 이는 자바 어플리케이션에서 gui, window 등을 사용하지 않도록 설정합니다.
configureHeadlessProperty();
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
Boolean.toString(this.headless)));
}
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
Java
복사
3.
스프링부트에서 SpringApplication 이 실행될 때 사용되는 리스너들을 가져와 처리합니다.
SpringApplicationRunListeners listeners = getRunListeners(args);
private SpringApplicationRunListeners getRunListeners(String[] args) {
ArgumentResolver argumentResolver = ArgumentResolver.of(SpringApplication.class, this);
argumentResolver = argumentResolver.and(String[].class, args);
List<SpringApplicationRunListener> listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class,
argumentResolver);
SpringApplicationHook hook = applicationHook.get();
SpringApplicationRunListener hookListener = (hook != null) ? hook.getRunListener(this) : null;
if (hookListener != null) {
listeners = new ArrayList<>(listeners);
listeners.add(hookListener);
}
return new SpringApplicationRunListeners(logger, listeners, this.applicationStartup);
}
Java
복사
•
리스너란 스프링에서 발생하는 이벤트를 감지하고 해당 이벤트에 대한 작업을 수행하는 객체를 의미합니다.
•
별도 설정 없이 등록되는 리스너들은 다음과 같습니다.
•
대표적인 예시로 LogginApplicationListener 를 살펴보면 다음과 같습니다. 우선 실행 과정과는 크게 연관이 없으니 다른 글에서 리스너에 대해 더 자세히 알아보도록 하겠습니다.
public class LoggingApplicationListener implements GenericApplicationListener
Java
복사
4.
SpringApplication 이 실행될 때, 리스너들에게 spring.boot.application.starting 이벤트를 알리고, 관련 작업을 수행합니다.
listeners.starting(bootstrapContext, this.mainApplicationClass);
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
(step) -> {
if (mainApplicationClass != null) {
step.tag("mainApplicationClass", mainApplicationClass.getName());
}
});
}
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
Consumer<StartupStep> stepAction) {
StartupStep step = this.applicationStartup.start(stepName);
this.listeners.forEach(listenerAction);
if (stepAction != null) {
stepAction.accept(step);
}
step.end();
}
Java
복사
•
spring.boot.application.starting 이벤트를 감지합니다.
•
그리고 mainApplicationClass 를 tag로 등록합니다.
6. ApplicationArgument, Enviroment, Banner 작업
•
ApplicationArguments 로 처음 자바 어플리케이션 실행 시 전달된 인자들을 캡슐화합니다.
•
Listener, BootStrapContext, ApplicationArguments 로 최종 스프링 실행 환경 관련 객체
•
ConfigurableEnvirotment 를 생성합니다. 그리고 배너를 출력합니다.
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = printBanner(environment);
Java
복사
7. ApplicationContext 등록 및 생성
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
Java
복사
createApplicationContext() 내부 과정을 자세히 살펴보면
a.
ApplicationContextFactory 의 구현체를 결정합니다. 구현체로는로는 Default , Reactive , ServletWeb 이 있고, 후보와 WebApplicationType 을 비교하여 구현체를 결정합니다.
private <T> T getFromSpringFactories(WebApplicationType webApplicationType,
BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class,
getClass().getClassLoader())) {
T result = action.apply(candidate, webApplicationType);
if (result != null) {
return result;
}
}
return (defaultResult != null) ? defaultResult.get() : null;
}
Java
복사
b. 이후 결정된 구현체 클래스(이 글에서는 Default ) 에서 ConfiguableApplicationContext 를 생성합니다.
@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
try {
return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create,
this::createDefaultApplicationContext);
}
catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
}
Java
복사
그리고 최종적으로 context.setApplicationStartup(this.applicationStartup); 에서 StartUp 객체를 생성합니다.
8. ApplicationContext 준비 & 초기화
정말 진짜 마지막으로… prepareContext 에서 지금까지 캡슐화한 객체들로 어플리케이션 컨텍스트를 준비하고 초기화합니다.
→ 그리고 refreshContext 메서드로 스프링 컨텍스트를 새로고침 합니다.
→ afterRefresh 메서드는 컨텍스트 새로고침 후 이런저런 추가 작업을 진행합니다.
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
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());
}
if (this.keepAlive) {
context.addApplicationListener(new KeepAlive());
}
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);
}
Java
복사
9. 실행
준비가 끝났습니다. 스프링부트 어플리케이션을 실행합니다.
startup.started();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);
}
listeners.started(context, startup.timeTakenToStarted());
callRunners(context, applicationArguments);
...
return context;
Java
복사