Spring Boot使用系列四(启动之前和启动之后)

本文主要探讨了Spring Boot在main方法执行完毕后为何仍能运行,原因是启动了一个非守护线程来保持应用运行。此外,文章还介绍了Spring Boot在main方法之前如何启动,包括在IDEA中直接启动和通过jar包启动的方式,并分析了jar包的内部结构。通过对源码的分析,揭示了Spring Boot启动流程的关键步骤,如创建ClassLoader和启动Spring Boot应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在前面两篇的文章中我们分析了从Main开始的启动过程ApplicationContext的刷新过程

1. 这还有一个小小的疑问:main函数都运行为了,为什么spring boot还能够运行呢?

针对这个问题,我们转换一下思路,为什么spring boot应用程序还在运行,那是因为jvm还没有推出,那什么情况下jvm会退出呢?

我们都知道: System.exit()Runtime.exit()可以直接导致当前JVM退出。还有一种情况就是所有的非daemon进程完全终止

那么我们可不可以猜测Spring Boot还能够继续运行的原因是由于还有非daemon进程没有终止

那我们用jstack分析一下一个启动的spring boot线程 jstack 9818 |grep tid |grep -v daemon
在我的电脑上能够看到如下信息:
排除掉其中的GC和JVM线程,我们发现了一个奇怪的线程server,那我们看一下server的详细信息呢?
在这里插入图片描述看到这么我们注意到最后一行NettyWebServer.run方法,我们跟进代码按一下这里面是什么呢?在这里插入图片描述在此我们找到了我们用jstack看到的server线程的由来,通过方法名startDaemonAwaitThread可以知道我们启动一个非daemon的线程来等待关闭spring boot线程。这就是为什么main方法运行完了,spring boot程序还能欧股继续运行的原因。

扩展一下,因为我的程序是基于spring cloud gateway的,底层是用户netty实现的nio。所以这个会进入NettyWebServer。如果是一般基于tomcat的会进入TomcatWebServer,tomcat也有相应的代码:
在这里插入图片描述在那怎么确实用户netty还是Tomcat或者undertow等webServer呢?从前面的启动过程中我们看到有这样的代码

	static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

这样就是我们看到不同的WebServer是通过classpath来判断的。

2.Spring boot运行main方法之前都干了什么呢?或者Spring Boot是怎么启动的呢?

1. 在idea中启动

在idea中我们启动一个spring boot程序后,我们用ps命令看一下情况,发现好长一串启动命令
在这里插入图片描述通常在IDEA中默认的启动方式是通过主类的main函数启动,所有依赖的jar都通过jdk的参数添加进来。这就是一般java的启动方式。

2.通过jar启动

在我们使用spring boot的过程中,我们经常都是成jar包后,通过java -jar xxx.jar来启动。打包成jar,一般都是用户spring boot的打包插件

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

我们加压一下打好的jar包:我们可以看到:

  • BOOT-INF/classes:存储的是spring-boot项目编写的java源码的class文件
  • BOOT-INF/lib :存储的是spring-boot依赖的jar包
  • META-INF/MANIFEST.MF: 记录了jar的信息,包括主类和spring添加的额外信息
  • META-INF/maven : 记录项目的maven信息,包括pom.xml和pom.properties
  • org/springframework: 是spring-boot-maven-plugin打包进去的信息,也是启动的入口
    从ETA-INF/MANIFEST.MF中我们看到了如下内容:
Main-Class: org.springframework.boot.loader.JarLauncher

说明通过此种方式的启动类是JarLauncher。这块的源码在spring-boot-tools的spring-boot-loader中。代码如下:

public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	static final String BOOT_INF_LIB = "BOOT-INF/lib/";

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		if (entry.isDirectory()) {
			return entry.getName().equals(BOOT_INF_CLASSES);
		}
		return entry.getName().startsWith(BOOT_INF_LIB);
	}

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}
}

通过源码的查看,我们看到类似的类结构
在这里插入图片描述我们知道还能通过properties和war的方式启动。由于这两种方式不常用,故不进一步分析。
接下来我们分析一下Launcher的launch的源码:

	protected void launch(String[] args) throws Exception {
		JarFile.registerUrlProtocolHandler();
		ClassLoader classLoader = createClassLoader(getClassPathArchives());
		launch(args, getMainClass(), classLoader);
	}
  • 注册Url协议的处理程序
  • 获取classpath下的所有归档文件,并创建ClassLoader。这块的内容比较多,此处就不详细分析,具体分析可以参考这篇文章
  • 最后启动spring boot程序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值