概要
Spring BootアプリをTomcatにデプロイして実行する場合、SpringBootServletInitializerを継承したクラスを作成することで実現するが、Servlet仕様でもないSpringBootServletInitializerを継承したクラスがなぜTomcatで実行されるのかについて述べる。
Servlet仕様との紐づけ
ずばりSpring Webに含まれるServletContainerInitializerを実装したSpringServletContainerInitializerの仕業だ。以下がそのクラスだ。
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のものに変更されている。
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
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以前でなければ正常に動作しない。