前言
我们都知道springboot默认内嵌tomcat容器,可以通过java -jar进行快速启动,也支持外置tomcat容器进行启动.那么这两者的启动原理是什么?
内嵌容器方式java -jar启动
java -jar做了什么
先要弄清楚java -jar命令做了什么,在oracle官网找到了该命令的描述:
If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.
翻译:
如果指定了-jar选项,则其参数是包含应用程序的类和资源文件的jar文件的名称。启动类必须由其源代码中的主类清单头指示。
总结:
- 使用-jar参数时,后面的参数是的jar文件名 如:java -jar spt-demo-0.0.1-SNAPSHOT.jar
- 该jar文件中包含的是class和资源文件
- 在manifest(清单)文件中有Main-Class的定义
- Main-Class的源码中指定了整个应用的启动类
一句话:java -jar会去找jar中的manifest文件,在那里面找到真正的启动类
Java没有提供任何标准的方式来加载嵌套的jar文件(即,它们本身包含在jar中的jar文件)
Jar包的打包插件及核心方法
Spring Boot项目的pom.xml文件中默认使用如下插件进行打包:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐maven‐plugin</artifactId>
</plugin>
</plugins>
</build>
执行maven clean package之后,会生成两个文件:
spring-boot-maven-plugin项目存在于spring-boot-tools目录中。
spring-boot-maven-plugin默认有5个goals: repackage、run、start、stop、build-info。
在打包的时候默认使用的是repackage。
spring-boot-maven-plugin的repackage能够将mvn package生成的软件包,再次打包为可执行的软件包,并将mvn package生成的软件包重命名为*.original。
manifest清单解析
找到打包的jar,进入META-INF/
使用解压工具打开
打成的jar文件整体目录结构:
├─.idea
│ └─libraries
├─BOOT-INF
│ ├─classes
│ │ └─com
│ │ └─itmck
│ │ └─springbootmq
│ │ ├─compnent
│ │ ├─config
│ │ └─controller
│ └─lib
├─META-INF
│ └─maven
│ └─com.itmck
│ └─springboot-mq
└─org
├─.idea
└─springframework
└─boot
└─loader
├─archive
├─data
├─jar
└─util
从MANIFEST.MF可以看到Main函数是JarLauncher,下面来分析它的工作流程。JarLauncher类的继承结构是:
class JarLauncher extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher extends Launcher
public class JarLauncher extends ExecutableArchiveLauncher {
public JarLauncher() {}
public static void main(String args[]) throws Exception {
//其主入口新建了 JarLauncher 并调用父类 Launcher 中的launch方法启动程序
(new JarLauncher()).launch(args);
}
}
通过Launcher/Launcher 可以看到
- 以FatJar为file作为入参,构造JarFileArchive对象。获取其中所有的资源目标,取得其Url,将这些URL作为参数, 构建了一个URLClassLoader。
- 以第一步构建的ClassLoader加载MANIFEST.MF文件中Start-Class指向的业务类,并且执行静态方法main。进而 启动整个程序。
```java public abstract class Launcher { protected void launch(String args[]) throws Exception {
} }JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = createClassLoader(getClassPathArchives());//1
launch(args, getMainClass(), classLoader);//2
- 通过getMainClass() 可以找到最终获取到Manifest清单里的Start-Class的value
**com.itmck.springbootmq.SpringbootMqApplication**<br />**进而启动springboot的main方法**
```java
public abstract class ExecutableArchiveLauncher extends Launcher {
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();//获取到清单
String mainClass = null;
if (manifest != null) {
//清单存在就获取启动类
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
} else {
return mainClass;
}
}
}
总结
- JarLauncher通过加载BOOT-INF/classes目录及BOOT-INF/lib目录下jar文件,实现了fat jar的启动。
- SpringBoot通过扩展JarFile、JarURLConnection及URLStreamHandler,实现了jar in jar中资源的加载.
- SpringBoot通过扩展URLClassLoader–LauncherURLClassLoader,实现了jar in jar中class文件的加载。
- WarLauncher通过加载WEB-INF/classes目录及WEB-INF/lib和WEB-INF/lib-provided目录下的jar文件,实现了war文 件的直接启动及web容器中的启动。
springboot源码解析
springboot#run()启动
找到通过run找到ConfigurableApplicationContext可以发现
找到通过ConfigurableApplicationContext找到SpringApplication 可以发现
总结:初始化信息
通过ConfigurableApplicationContext找到run
可以看到如下:
SpringBoot启动事件
- SpringApplicationRunListeners的唯一实现是EventPublishingRunListener;
- 整个SpringBoot的启动,流程就是各种事件的发布,调用EventPublishingRunListener中的方法。
SpringBoot启动事件
创建和配置环境
准备ApplicationContext
发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
SpringBoot已启动事件
SpringBoot现在可以处理接受的请求事件
总结:
- 初始化信息,包括web应用的类型获取,获取ApplicationListener与ApplicationContextInitializer
- 获取运行监听器运行器,发布SpringBoot启动事件
- 封装命令行参数,获取配置信息
- 实例化上下文,准备上下文,发布EventPublishingRunListener事件
- 刷新容器,加载ioc容器
- 发布已启动事件监听