Programming/Spring Boot

스프링부트 내장 톰캣(서블릿 컨테이너) 실행되는 과정

Jan92 2024. 2. 6. 22:00

Spring Boot 내장 서블릿 컨테이너가 실행되는 과정

Spring Boot embedded tomcat

최근 스프링부트 프로젝트의 내장 톰캣을 제거하는 과정에서 '내장 서블릿 컨테이너가 자동으로 실행되는 과정'이 궁금하여 관련 내용을 디버깅하며 정리해 보았습니다.

 


SpringApplication.run()

public ConfigurableApplicationContext run(String... args) {
    // ...
    ConfigurableApplicationContext context = null;
    
        try {
            // ...
            // 1. ApplicationContext 생성
            context = createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);    

            // 2. ApplicationContext Refresh 과정에서 WebServer 생성
            refreshContext(context);	

            // ...
        }
    return context;
}

(SpringApplication.run() method 코드 중 일부)

 

먼저 'SpringApplication.run()' 메서드가 실행되었을 때 내부적으로 여러 가지 작업이 이루어지는데요.

여기서는 내장 톰캣(또는 다른 내장 서블릿 컨테이너)이 실행되는 과정에 중점을 두고 살펴보겠습니다.

 

SpringApplication.run() 메서드가 실행되면 '1. IOC 컨테이너인 ApplicationContext를 생성하고 초기화'하여 반환하게 됩니다.

그리고 '2. ApplicationContext를 Refresh 하는 과정에서 WebServer이 생성되고 실행'되는데요.

 

WebServer가 생성되고 실행되는 곳을 확인해 보기 위해 'WebApplicationType.SERVLET' 기준으로 refreshContext() 메서드를 따라가 보면 'ServletWebServerApplicationContext 클래스의 refresh() method' -> 'AbstractApplicationContext 추상 클래스의 refresh() method' 순서로 실행되는 것을 확인할 수 있습니다.

 

(WebApplicationType.SERVLET 기준으로 AnnotationConfigServletWebServerApplicationContext 클래스가 ApplicationContext 구현체로 생성됩니다.)

 

 


onRefresh() method

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        // WebServer 생성
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

(ServletWebServerApplicationContext 클래스의 onRefresh() method)

 

이어서 'AbstractApplicationContext 클래스의 refresh() 동작 과정에서 onRefresh() method'가 실행되고, 다시 onRefresh()를 따라가 보면 'ServletWebServerApplicationContext 클래스의 onRefresh() method'가 실행되는 것을 확인할 수 있는데요.

 

해당 onRefresh() 메서드 코드에서 WebServer를 생성하는 'createWebServer()' 메서드가 실행되는 것을 볼 수 있습니다.

 

 


createWebServer() method

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        StartupStep createWebServer = getApplicationStartup().start("spring.boot.webserver.create");
        
        // 등록된 webServer, servletContext가 없을 경우
        ServletWebServerFactory factory = getWebServerFactory();
        createWebServer.tag("factory", factory.getClass().toString());
        this.webServer = factory.getWebServer(getSelfInitializer());
        createWebServer.end();
        getBeanFactory().registerSingleton("webServerGracefulShutdown",
                new WebServerGracefulShutdownLifecycle(this.webServer));
        getBeanFactory().registerSingleton("webServerStartStop",
                new WebServerStartStopLifecycle(this, this.webServer));
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}

(ServletWebServerApplicationContext 클래스의 createWebServer() method)

 

createWebServer() 메서드에서는 등록된 servletContext가 없을 경우 'getWebServerFactory()'를 통해 'ServletWebServerFactory'를 가져오는데요.

 

 

protected ServletWebServerFactory getWebServerFactory() {
    // Use bean names so that we don't consider the hierarchy
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    if (beanNames.length == 0) {
        throw new MissingWebServerFactoryBeanException(getClass(), ServletWebServerFactory.class,
            WebApplicationType.SERVLET);
    }
    if (beanNames.length > 1) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
            + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
    }
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

(ServletWebServerApplicationContext 클래스의 getWebServerFactory() method)

 

'getWebServerFactory()' 메서드를 살펴보면, BeanFactory에서 ServletWebServerFactory(@FunctionalInterface) 인터페이스를 클래스 타입으로 등록된 빈 이름을 가져와서 해당 ServletWebServerFactory 구현체를 반환하게 됩니다.

 

 

tomcatServletWebServerFactory

해당 예시에서는 빈으로 등록된 'tomcatServletWebServerFactory'가 검색되어 반환되며, 이후 createWebServer() 메서드 동작 과정 중 'factory.getWebServer(getSelfInitializer())' 부분에서 톰캣이 생성되고 실행됩니다.

(gerWebServer() 메서드 내부의 톰캣 생성 및 실행 코드는 생략하겠습니다.)

 

그렇다면 TomcatServletWebServerFactory는 어디서 생성되어 Bean으로 등록되는 것일까요?

 

 


ServletWebServerFactoryAutoConfiguration class

@AutoConfiguration(after = SslAutoConfiguration.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

	@Bean
	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
			ObjectProvider<WebListenerRegistrar> webListenerRegistrars,
			ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers, ObjectProvider<SslBundles> sslBundles) {
		return new ServletWebServerFactoryCustomizer(serverProperties, webListenerRegistrars.orderedStream().toList(),
				cookieSameSiteSuppliers.orderedStream().toList(), sslBundles.getIfAvailable());
	}

	@Bean
	@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
	public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
			ServerProperties serverProperties) {
		return new TomcatServletWebServerFactoryCustomizer(serverProperties);
	}

        // ...
}

(ServletWebServerFactoryAutoConfiguration class 코드 중 일부)

 

TomcatServletWebServerFactory 외 Jetty, Undertow의 ServletWebServerFactory는 스프링 부트의 대표적인 기능인 AutoConfiguration을 통해 등록되며 해당 내용은 'ServletWebServerFactoryAutoConfiguration' 클래스에서 확인할 수 있습니다.

(AutoConfiguration에 대한 자세한 내용은 아래 링크 자료를 참고하시면 좋을 것 같습니다.)

 

 

***

정리를 해보자면, 내장 서블릿 컨테이너는 'SpringApplication.run() method' 동작 과정 중 ApplicationContext를 생성하고 초기화하는 과정에서 위와 같은 코드로 인해 생성되고 동작하게 되는 것입니다.

 

 

여기까지 스프링 부트에서 내장 서블릿 컨테이너가 생성되고 실행되는 과정이 궁금하여 관련 코드를 따라가 보았습니다.

내용 중 잘못된 부분이나 궁금하신 부분은 댓글 남겨주시면 확인하겠습니다. 감사합니다.

 

 

 

< AutoConfiguration 관련 자료 >

2023.07.21 - [Programming/Spring Boot] - Spring Boot 자동 구성 AutoConfiguration 동작 원리 파헤치기