SpringBootServletInitializerがTomcatで実行される仕組み

# 概要
Spring BootアプリをTomcatにデプロイして実行する場合、SpringBootServletInitializerを継承したクラスを作成することで実現するが、Servlet仕様でもないSpringBootServletInitializerを継承したクラスがなぜTomcatで実行されるのかについて述べる。

# Servlet仕様との紐づけ
ずばりSpring Webに含まれるServletContainerInitializerを実装したSpringServletContainerInitializerの仕業だ。以下がそのクラスだ。
```java:SpringServletContainerInitializer.java
package org.springframework.web;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;

import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.HandlesTypes;

import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = Collections.emptyList();

		if (webAppInitializerClasses != null) {
			initializers = new ArrayList<>(webAppInitializerClasses.size());
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}
```
ServletContainerInitializerを実装したクラスのonStartupメソッドはTomcatによってデプロイ時に実行される。
このメソッドは最初にWebApplicationInitializerインターフェースを実装したクラスを探してリストに格納し、その後にそれらのonStartupメソッドを順に実行する。実行の順番はSpringの@Orderアノテーションで並べた順になる。
SpringBootServletInitializerはWebApplicationInitializerインターフェースを実装しているので、そのonStartUpが実行されSpringのApplicationContextが作成されDIコンテナなどが準備されSpring Bootアプリが起動する。

# Spring BootとTomcatのバージョンに注意
Spring Boot 3.0.0から、依存するSpring Webのバージョンが6に上がったことによりSpringServletContainerInitializerが使用するServletContainerInitializerがJava EEのものからJakarta EEのものに変更されている。
```java:SpringServletContainerInitializer.java（Spring Web 5.3.x）
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
```

```java:SpringServletContainerInitializer.java（Spring Web 6.0.0-RC2）
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.HandlesTypes;
```
そのためSpring Boot 3.0.0で作ったwarはTomcat 10.0.x以上でなければ正常に動作しない。逆にSpring Boot 2.7.x以前で作ったwarはTomcat 9.x以前でなければ正常に動作しない。