SpringBootServletInitializer是在Tomcat中执行的机制
总结
如果要将Spring Boot应用程序部署到Tomcat并执行,可以通过创建一个继承SpringBootServletInitializer的类来实现。然而,为什么继承SpringBootServletInitializer并非Servlet规范的类也能在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及之前版本上正常运行。