IoC Conatiner in SpringApplication.run() - 1

koonlx·2024년 11월 9일

Spring

목록 보기
5/7
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를 반환하여 애플리케이션이 완전히 실행된 상태임을 알린다.

SpringApplicationContext(IoC Container)

create(): 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에서 유효한 결과가 없을 때 호출된다.

  • 1단계: getFromSpringFactories 호출
    create 메서드가 호출되면, getFromSpringFactories 메서드로 넘어가서 환경에 맞는 ApplicationContextFactory를 찾아서 결과를 생성하게 된다.

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;
}
  • 2단계: SpringFactoriesLoader로 ApplicationContextFactory 구현체 로드
    SpringFactoriesLoader.loadFactories 메서드는 META-INF/spring.factories 파일에 등록된 모든 ApplicationContextFactory 구현체를 로드하여, 반복문(for 루프)에서 candidate 변수에 하나씩 할당한다.
    각 ApplicationContextFactory 구현체는 애플리케이션 환경에 따라 서로 다른 ApplicationContext를 생성할 수 있도록 다양한 종류가 있을 수 있다.
  • 3단계: action.apply로 ApplicationContext 생성 시도
    action.apply(candidate, webApplicationType)는 ApplicationContextFactory 구현체(candidate)와 WebApplicationType을 인수로 받아 해당 팩토리가 제공하는 ApplicationContext를 생성한다.
    action은 ApplicationContextFactory::create 메서드 참조로 전달되었기 때문에, 각 ApplicationContextFactory의 create 메서드를 호출하게 된다.
    result가 null이 아니면 즉시 반환된다. 즉, 가장 먼저 유효한 ApplicationContext를 반환하고, 루프는 종료된다.
  • 4단계: defaultResult 반환
    모든 ApplicationContextFactory에서 null이 반환되면, defaultResult(this::createDefaultApplicationContext)를 통해 기본 ApplicationContext를 생성한다.
    defaultResult.get()를 호출하여 기본 ApplicationContext가 반환된다.

createDefaultApplicationContext(): 기본 ApplicationContext 생성

만약 ApplicationContextFactory에서 유효한 ApplicationContext를 생성하지 못했다면, createDefaultApplicationContext 메서드가 호출되어 기본 ApplicationContext를 제공한다.

private ConfigurableApplicationContext createDefaultApplicationContext() {
    if (!AotDetector.useGeneratedArtifacts()) {
        return new AnnotationConfigApplicationContext();
    }
    return new GenericApplicationContext();
}
  • 5단계: AOT 환경 여부 확인 및 기본 컨텍스트 선택
    AotDetector.useGeneratedArtifacts()는 애플리케이션이 AOT(Ahead-of-Time) 컴파일을 통해 미리 생성된 아티팩트를 사용하는지 확인한다.
    AOT 환경이 아닌 경우 AnnotationConfigApplicationContext가 반환되며, 이는 애너테이션 기반 설정이 가능한 표준 ApplicationContext이다.
    반대로 AOT 환경인 경우 GenericApplicationContext가 반환됩니다. 이는 경량화된 설정으로, 메모리와 속도를 최적화하는 역할을 한다.

전체 실행 흐름

  • create 메서드 호출: getFromSpringFactories 메서드를 통해 적절한 ApplicationContext를 찾도록 지시.
  • getFromSpringFactories에서 팩토리 로드: SpringFactoriesLoader를 통해 ApplicationContextFactory 구현체를 모두 로드.
  • action.apply로 ApplicationContext 생성 시도: 각 팩토리를 통해 ApplicationContext를 생성하고, 유효한 첫 번째 결과를 반환.
  • 기본 ApplicationContext 반환: 유효한 ApplicationContextFactory가 없을 경우 createDefaultApplicationContext가 기본값을 반환.
  • AOT 여부에 따른 최종 컨텍스트 선택: AotDetector로 AOT 환경을 감지하여 적절한 ApplicationContext를 선택.
  • 이 흐름을 통해 Spring Boot는 애플리케이션 환경에 따라 최적의 ApplicationContext를 유연하게 생성하고, 이를 통해 애플리케이션이 안정적으로 실행될 수 있도록 보장.

다음글에 이어서 어떻게 Bean의 생명주기를 관리하는지 설명하겠다.

profile
Server Developer

0개의 댓글