集成说明
启动类改造
启动类去继承 SpringBootServletInitializer,重写configure 方法,如下所示:
package com.xlw.demo.web;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* @author : xlw
* @since : 2024-09-29 10:52
* DemoApplication
*/
@SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
try {
SpringApplication.run(DemoApplication .class, args);
} catch (Throwable e) {
e.printStackTrace();
}
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(DemoApplication .class);
}
}
pom排除springboot 自带的tomcat相关jar
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
注:如果自己工程里面引入了与servlet相关的jar, 需要处理与启动的tomcat中jar版本冲突问题
完成上述两步之后,部署启动即可
完成上述两步之后,我们在启动tomcat时,tomcat会加载到springboot 启动相关的类,之前springboot配置不用调整即可正常使用tomcat启动springboot 工程。
tomcat启动springboot 原理
tomcat提供了JDK原生的SPI
jdk提供了原生的spi(javax.servlet.ServletContainerInitializer)接口,让第三方去实现,tomcat在启动时,会加载到对应的SPI 接口,进行第三方相关逻辑的调用执行(基于servlet3.0)
spring实现了ServletContainerInitializer 接口
从上图中,能够看到实现类SpringServletContainerInitializer,以及类上面有注解@HandlesTypes(WebApplicationInitializer.class)
这个注解的作用是,在servlet 容器调用该SPI 实现时,会把WebApplicationInitializer 的实现类传递给SpringServletContainerInitializer,
servlet容器执行SpringServletContainerInitializer.onStartup 方法
把WebApplicationInitializer 的实现类传递给onStartup 方法,然后在该方法中调用具体实现类的逻辑实现,如下图所示:
@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);
}
}
通过上述逻辑去完成第三方(此处spring)容器加载的相关逻辑调用
在第一步继承SpringBootServletInitializer 的实现类DemoApplication
去看SpringBootServletInitializer 源码,可以看到该类实现了WebApplicationInitializer,如下图所示
总结
综上所述,我们可以得出结论,
- 在servlet容器(tomcat)启动时,会调用SPI的相关逻辑调用到spring提供的实现类SpringServletContainerInitializer
- 在该类的实现方法onStartup时,会把该类上注解中WebApplicationInitializer 接口的实现类传递给该方法的参数(webAppInitializerClasses)
- 在该方法中调用具体的(WebApplicationInitializer )实现方法onStartup,调用到spring容器加载逻辑。
- 从而完成了tomcat加载springboot中的相关逻辑的执行,完成springboot应用的tomcat发布
- 我们想要在servlet容器启动时,想要让容器去调用我们自定义的一些实现,就可以去实现ServletContainerInitializer 接口,里面去做我们自己的实现。demo如下所示:
创建接口类
package com.xlw.test;
/**
* @author : xlw
* @since : 2024-09-29 15:40
* TestInterface
*/
public interface TestInterface {
}
创建ServletContainerInitializer 实现类
添加上注解@HandlesTypes(TestInterface.class),这样在调用该实现类时就会把TestInterface 的实现类传递给 onStartup 方法,我们就可以在TestInterface 做自定义的一些逻辑处理
package com.xlw.test;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import java.util.ServiceLoader;
import java.util.Set;
/**
* @author : xlw
* @since : 2024-09-29 15:35
* MyTest
*/
@HandlesTypes(TestInterface.class)
public class MyTest implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println("被执行了");
ServiceLoader<MainTest> load = ServiceLoader.load(MainTest.class);
for (MainTest mainTest : load) {
System.out.println(mainTest);
}
}
}
在resources先创建META-INF/services 文件夹,添加SPI接口
以javax.servlet.ServletContainerInitializer为文件名,文件内部的内容为我们的实现类
com.xlw.test.MyTest