[Spring MVC #1] ApplicationContext 생성 과정

mangoo·2023년 9월 26일
0

DemoApplication - main()

Spring Boot 프로젝트에서 애플리케이션을 실행시키려면 아래 함수를 실행시켜야 한다.

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

SpringApplication - run()

DemoApplication의 main() 메서드는 SpringApplication 클래스의 run() 메서드를 호출한다. SpringApplication 클래스의 주요 클래스 변수와 메서드는 아래와 같다.

public class SpringApplication {

	ConfigurableApplicationContext context = null;
	private ConfigurableEnvironment environment;
	private WebApplicationType webApplicationType;

	public ConfigurableApplicationContext run(String... args) {
		// [1]  ApplicationContext 생성 및 ApplicationStartup 설정
		context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);	

		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		refreshContext(context);
       	afterRefresh(context, applicationArguments);
        
        listeners.started(context, timeTakenToStartup);
        callRunners(context, applicationArguments);
       		  
		return context;
	}
}

이번 포스팅에서는 [1] ApplicationContext 생성 및 ApplicationStartup 설정에 대해 다뤄보려고 한다.


ApplicationContext 생성

// SpringApplication.java
context = createApplicationContext();

SpringApplication에서 ApplicationContext를 생성하는 부분이다. createApplicationContext() 내부에서는 webApplicationType 인자와 함께 ApplicationContextFactory 클래스에게 생성을 위임한다.

// SpringApplication.java
protected ConfigurableApplicationContext createApplicationContext() {
	return this.applicationContextFactory.create(this.webApplicationType); // this.webApplicationType: "SERVLET"
}
        
private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;  

// ApplicationContextFactory.java
ApplicationContextFactory DEFAULT = new DefaultApplicationContextFactory();

applicationContextFactory 클래스 변수는 ApplicationContextFactory의 DEFAULT 상수를 참조하고,
해당 상수는 DefaultApplicationContextFactory를 참조한다. 결국, createApplciationContext() 메서드는 [1] DefaultApplicationContextFactory의 create() 메서드를 호출해 그 결과값을 리턴하는 것이다.

ApplicationContextFactory

ApplicationContextFactory는 ConfigurableApplicationContext를 생성하기 위해 SpringApplicatiuon에서 사용하는 함수형 인터페이스이다.

// ApplicationContextFactory.java
@FunctionalInterface
public interface ApplicationContextFactory {
   ConfigurableApplicationContext create(WebApplicationType webApplicationType);
}

DefaultApplicationContextFactory의 create() 메서드 내부에서는 [2] getFromSpringFactories() 메서드를 호출한다.

// DefaultApplicationContextFactory.java
@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);
	}
}

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;
}

getFromSpringFactories() 메서드의 전체적인 로직을 살펴보면 ApplicationContextFactory의 구현체를 순회하며 인자로 받은 ApplicationContextFactory::create 람다(action 변수)를 실행해, 결과값이 null이 아니면 해당 값을 반환한다.

SpringFactoriesLoader.loadFatories()

public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
	return forDefaultResourceLocation(classLoader).load(factoryType);
}

인자로 받은 factoryType을 기준으로 META-INF/spring.factories에서 해당 타입의 factory 구현체를 로드하고 인스턴스화 해서 반환한다.
앞서 loadFactories() 메서드를 호출할 때 ApplicationContextFactory 타입을 넘겼기 때문에 META-INF/spring.factories에 정의된 ReactiveWebServerApplicationContextFactoryServletWebServerApplicationContextFactory가 담긴 리스트가 반환된다는 것을 알 수 있다.

// META-INF/spring.factories
# Application Context Factories
org.springframework.boot.ApplicationContextFactory=\
org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContextFactory,\
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContextFactory

다시 돌아가 각 구현체별로 create() 메서드가 실행되는 부분을 살펴보면 인자로 받은 WebApplicationType에 따라 적절한 ConfigurableApplicationContext 객체를 생성해 리턴한다.

// ServletWebServerApplicationContextFactory.java
@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
	return (webApplicationType != WebApplicationType.SERVLET) ? null : createContext();
}

private ConfigurableApplicationContext createContext() {
	if (!AotDetector.useGeneratedArtifacts()) {
		return new AnnotationConfigServletWebServerApplicationContext();
	}
	return new ServletWebServerApplicationContext();
}

// ReactiveWebSererApplicationContextFactory.java
@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
	return (webApplicationType != WebApplicationType.REACTIVE) ? null : createContext();
}

private ConfigurableApplicationContext createContext() {
	if (!AotDetector.useGeneratedArtifacts()) {
		return new AnnotationConfigReactiveWebServerApplicationContext();
	}
	return new ReactiveWebServerApplicationContext();
}

WebApplicationType

WebApplicationType은 아래와 같이 3가지가 있다. 해당 타입에 따라 생성되는 ApplicationContext가 달라진다.

public enum WebApplicationType {
	NONE,
	SERVLET,
	REACTIVE;
}
  1. Spring MVC가 존재한다면 SERVLET으로 동작해 AnnotationConfigWServletWebServerApplicationContext가 생성된다.
  2. Spring MVC가 존재하지 않고 Spring Webflux가 존재한다면 REACTIVE로 동작해 AnnotationConfigReactiveWebServerApplicationContext가 생성된다.
  3. 이외에는 NONE으로 동작해 AnnotationConfigApplicationContext가 생성된다.

!AotDetector.useGeneratedArtifcats()

public static boolean useGeneratedArtifacts() {
	return (inNativeImage || SpringProperties.getFlag(AOT_ENABLED));
}

런타임에 AOT 최적화를 고려해야 하는지 여부를 결정한다. Native Image로 빌드하거나 따로 Spring property를 설정하지 않았다면 false를 리턴한다.

현재 포스팅의 예제에서는 Spring MVC를 사용하고 있고, AOT 관련 설정을 따로 하지 않았기 때문에 getFromSpringFactories()에서 AnnotationConfigServletWebServerApplicationContext가 리턴될 것이다. 지금까지 타고 온 흐름을 거슬러 올라가면 결국 SpringApplication의 context 클래스 변수에 AnnotationConfigServletWebServerApplicationContext 객체가 담기게 된다.


ApplicationStartup 설정

애플리케이션이 실행되는 동안 SpringApplicationApplicationContext는 애플리케이션 라이프 사이클, 빈 라이프 사이클, 애플리케이션 이벤트 처리와 관련된 많은 작업들을 진행한다. ApplicationStartup은 애플리케이션 실행 단계를 추적하고 데이터를 수집할 수 있도록 도와주는 인터페이스이다.

ApplicationStartup 인터페이스

public interface ApplicationStartup {
	ApplicationStartup DEFAULT = new DefaultApplicationStartup(); 
	StartupStep start(String name);
}

구현체로는 DefaultApplicationStartup, BufferingApplicationStartup, FlightRecorderApplicationStartup가 있고 아래와 같은 방식으로 설정할 수 있다.

@SpringBootApplication
public class Demo {
	public static void main(String[] args) {
    	SpringApplication application = new SpringApplication(Demo.class);
        application.setApplicationStartup(new BufferingApplicationStartup(2048));
        application.run(args);

ApplicationStartup 메트릭 모니터링 예시는 여기에서 확인할 수 있다.

SpringApplication 코드를 보면 아래와 같이 applicationStartup 클래스 변수를 인자로 넘겨 context에 설정한다.

// SpringApplication.java
context.setApplicationStartup(this.applicationStartup);	

SpringApplication에 선언된 applicationStartup 필드를 확인해보면 아무런 역할을 수행하지 않는 DefaultApplicationStartup을 생성하는 것을 알 수 있다.

// SpringApplication.java
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;

// ApplicationStartup.java
ApplicationStartup DEFAULT = new DefaultApplicationStartup();

[1-1]에서 생성한 context는 모두 GenericApplication과 AbstractApplicationContext를 상속하고 있기 때문에 아래와 같은 흐름으로 코드가 실행된다.

// GenericApplicationContext.java
@Override
public void setApplicationStartup(ApplicationStartup applicationStartup) {
	super.setApplicationStartup(applicationStartup);   // (1) 
	this.beanFactory.setApplicationStartup(applicationStartup);   // (2)
}

// (1) AbstractApplicationContext.java
@Override
public void setApplicationStartup(ApplicationStartup applicationStartup) {
	Assert.notNull(applicationStartup, "ApplicationStartup must not be null");
	this.applicationStartup = applicationStartup;
}

// (2) AbstractBeanFactory.java
@Override
public void setApplicationStartup(ApplicationStartup applicationStartup) {
	Assert.notNull(applicationStartup, "applicationStartup must not be null");
	this.applicationStartup = applicationStartup;
}

결론

애플리케이션의 main() 메서드를 호출하면 SpringApplication -> ApplicationContextFactory -> DefaultApplicationContextFactory를 거치게 되고 WebApplicationType에 따라 적절한 context가 생성되는 것이다.

또한, SpringApplication 클래스 변수를 구체 타입이 아닌 ConfigurableEnvironmnet, ConfigurableApplicationContext 인터페이스 타입으로 선언했기 때문에 상황에 따라 유연하게 값을 할당할 수 있는 것이다.

ConfigurableApplicationContext context = null;
private ConfigurableEnvironment environment;

다음 포스팅에서는 [2] Context 후처리 작업 및 리프레시 단계를 위한 전처리 작업[3] Context 리프레시에 대해 다뤄보자.


Reference

0개의 댓글