package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
이 코드는 Spring Boot Project를 생성하면 자동으로 생성되는 Application 파일이다. Spring Framework의 시작 지점인 이 부분에서부터 Spring의 코드를 분석할 것이다.
public ConfigurableApplicationContext run(String... args) {
Startup startup = Startup.create();
if (this.registerShutdownHook) {
SpringApplication.shutdownHook.enableShutdownHookAddition();
}
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
startup.started();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);
}
listeners.started(context, startup.timeTakenToStarted());
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, listeners);
}
try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, null);
}
return context;
}
Startup startup = Startup.create();
이 구문은 Startup 인스턴스를 생성하여 애플리케이션 시작 시점을 기록하는 역할을 한다. 이 인스턴스는 애플리케이션의 실행 시간을 측정하는 데 사용될 수 있다.
if (this.registerShutdownHook) { ... }
애플리케이션 종료 후 클린업 작업을 위한 shutdown hook을 등록할지 결정하는 부분이다. registerShutdownHook이 true일 경우, SpringApplication의 shutdownHook 객체를 통해 애플리케이션 종료 시 shutdown hook을 추가한다.
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
이 구문은 애플리케이션 부팅 시 필요한 DefaultBootstrapContext 객체를 생성한다. BootstrapContext는 부트스트래핑 과정에서 사용할 수 있는 컨텍스트로, ApplicationContext 초기화 전에 필요한 설정들을 제공한다.
ConfigurableApplicationContext context = null;
이후에 생성될 ApplicationContext 인스턴스를 참조할 변수를 선언합니다. 이 변수는 이후 로직에서 할당된다.
configureHeadlessProperty();
이 메소드는 java.awt.headless 속성을 설정하여 그래픽 환경이 없는 서버에서도 애플리케이션이 실행될 수 있도록 한다.
SpringApplicationRunListeners listeners = getRunListeners(args);
getRunListeners 메소드를 통해 애플리케이션 실행 이벤트를 청취하는 리스너들을 가져온다. 이 리스너들은 애플리케이션의 라이프사이클 이벤트(시작, 환경 설정 등)를 감지하고 처리할 수 있다.
listeners.starting(bootstrapContext, this.mainApplicationClass);
리스너들에게 애플리케이션이 시작되고 있음을 알린다. starting 메소드는 부트스트래핑 과정의 초기 단계에서 호출되며, bootstrapContext와 mainApplicationClass를 전달한다.
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
전달받은 args 인자를 ApplicationArguments 형태로 변환한다. 이를 통해 애플리케이션 인자에 접근할 수 있다.
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
prepareEnvironment 메소드를 통해 애플리케이션의 실행 환경을 준비한다. 이 환경 객체에는 시스템 및 사용자 설정이 포함되며, 리스너들에게 해당 환경이 준비 중임을 알린다.
Banner printedBanner = printBanner(environment);
Spring 애플리케이션의 배너를 환경 설정에 맞추어 출력한다.
context = createApplicationContext();
애플리케이션을 실행할 ApplicationContext 인스턴스를 생성한다. 이 인스턴스는 빈을 관리하고 의존성을 주입하는 핵심적인 역할을 한다.
context.setApplicationStartup(this.applicationStartup);
applicationStartup 객체를 컨텍스트에 설정하여 애플리케이션의 시작 시간을 추적할 수 있게 한다.
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
prepareContext 메소드는 ApplicationContext를 설정하는 작업을 수행한다. 이 메소드에서는 environment, listeners, applicationArguments 및 printedBanner를 전달받아 애플리케이션 초기화에 필요한 설정을 수행한다.
refreshContext(context);
ApplicationContext를 초기화하고 활성화하는 작업을 수행한다. 이 메소드는 실제로 빈을 로드하고 애플리케이션의 설정을 완료한다.
afterRefresh(context, applicationArguments);
컨텍스트 초기화가 완료된 후 실행되는 메소드로, 추가적인 작업을 수행할 수 있다.
startup.started();
Startup 객체의 started 메소드를 호출하여 애플리케이션의 시작이 완료되었음을 기록한다.
if (this.logStartupInfo) { ... }
logStartupInfo가 true인 경우, StartupInfoLogger를 통해 애플리케이션 시작 정보를 로깅한다.
listeners.started(context, startup.timeTakenToStarted());
리스너들에게 애플리케이션이 시작되었음을 알린다.
callRunners(context, applicationArguments);
애플리케이션이 정상적으로 시작된 후 ApplicationRunner 및 CommandLineRunner 인터페이스를 구현한 빈을 실행한다.
예외 처리 블록
try-catch 블록으로 예외가 발생할 경우 handleRunFailure 메소드를 통해 예외를 처리하며, 실행 중 발생하는 모든 오류를 처리한다.
listeners.ready(context, startup.ready());
애플리케이션이 모든 준비를 완료한 후 리스너들에게 준비 완료 상태를 알린다.
return context;
마지막으로 생성된 ApplicationContext를 반환하여 애플리케이션이 완전히 실행된 상태임을 알린다.
코드의 실행은 create 메서드에서 시작된다. 이 메서드는 Spring Boot 애플리케이션의 ApplicationContext 인스턴스를 생성하는 역할을 한다.
@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
try {
return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create,
this::createDefaultApplicationContext);
}
catch (Exception ex) {
throw new IllegalStateException("Unable to create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
}
create 메서드는 getFromSpringFactories 메서드를 호출하여 적절한 ApplicationContext를 반환받는다.
첫 번째 매개변수 webApplicationType은 애플리케이션의 유형(SERVLET, REACTIVE, NONE 중 하나)을 나타낸다.
두 번째 매개변수 ApplicationContextFactory::create는 ApplicationContextFactory에 정의된 create 메서드를 참조하는 메서드 참조이다.
세 번째 매개변수 this::createDefaultApplicationContext는 기본 ApplicationContext를 생성하기 위한 메서드 참조로, ApplicationContextFactory에서 유효한 결과가 없을 때 호출된다.
create 메서드가 호출되면, getFromSpringFactories 메서드로 넘어가서 환경에 맞는 ApplicationContextFactory를 찾아서 결과를 생성하게 된다.getFromSpringFactories는 SpringFactoriesLoader를 사용해 ApplicationContextFactory 구현체들을 로드하고, WebApplicationType에 맞는 ApplicationContext를 생성하도록 구성되어 있다.
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;
}
SpringFactoriesLoader.loadFactories 메서드는 META-INF/spring.factories 파일에 등록된 모든 ApplicationContextFactory 구현체를 로드하여, 반복문(for 루프)에서 candidate 변수에 하나씩 할당한다.ApplicationContextFactory 구현체는 애플리케이션 환경에 따라 서로 다른 ApplicationContext를 생성할 수 있도록 다양한 종류가 있을 수 있다.action.apply(candidate, webApplicationType)는 ApplicationContextFactory 구현체(candidate)와 WebApplicationType을 인수로 받아 해당 팩토리가 제공하는 ApplicationContext를 생성한다.action은 ApplicationContextFactory::create 메서드 참조로 전달되었기 때문에, 각 ApplicationContextFactory의 create 메서드를 호출하게 된다.result가 null이 아니면 즉시 반환된다. 즉, 가장 먼저 유효한 ApplicationContext를 반환하고, 루프는 종료된다.ApplicationContextFactory에서 null이 반환되면, defaultResult(this::createDefaultApplicationContext)를 통해 기본 ApplicationContext를 생성한다.defaultResult.get()를 호출하여 기본 ApplicationContext가 반환된다.만약 ApplicationContextFactory에서 유효한 ApplicationContext를 생성하지 못했다면, createDefaultApplicationContext 메서드가 호출되어 기본 ApplicationContext를 제공한다.
private ConfigurableApplicationContext createDefaultApplicationContext() {
if (!AotDetector.useGeneratedArtifacts()) {
return new AnnotationConfigApplicationContext();
}
return new GenericApplicationContext();
}
AotDetector.useGeneratedArtifacts()는 애플리케이션이 AOT(Ahead-of-Time) 컴파일을 통해 미리 생성된 아티팩트를 사용하는지 확인한다.AnnotationConfigApplicationContext가 반환되며, 이는 애너테이션 기반 설정이 가능한 표준 ApplicationContext이다.GenericApplicationContext가 반환됩니다. 이는 경량화된 설정으로, 메모리와 속도를 최적화하는 역할을 한다.
- create 메서드 호출: getFromSpringFactories 메서드를 통해 적절한 ApplicationContext를 찾도록 지시.
- getFromSpringFactories에서 팩토리 로드: SpringFactoriesLoader를 통해 ApplicationContextFactory 구현체를 모두 로드.
- action.apply로 ApplicationContext 생성 시도: 각 팩토리를 통해 ApplicationContext를 생성하고, 유효한 첫 번째 결과를 반환.
- 기본 ApplicationContext 반환: 유효한 ApplicationContextFactory가 없을 경우 createDefaultApplicationContext가 기본값을 반환.
- AOT 여부에 따른 최종 컨텍스트 선택: AotDetector로 AOT 환경을 감지하여 적절한 ApplicationContext를 선택.
- 이 흐름을 통해 Spring Boot는 애플리케이션 환경에 따라 최적의 ApplicationContext를 유연하게 생성하고, 이를 통해 애플리케이션이 안정적으로 실행될 수 있도록 보장.
다음글에 이어서 어떻게 Bean의 생명주기를 관리하는지 설명하겠다.