Spring Boot 应用使用tomcat启动实现及原理

集成说明

启动类改造

启动类去继承 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 接口spring 实现对servlet容器启动时SPI

在这里插入图片描述
从上图中,能够看到实现类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
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值