在前面两篇的文章中我们分析了从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程序