前言

我们都知道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文件中默认使用如下插件进行打包:

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring‐boot‐maven‐plugin</artifactId>
  6. </plugin>
  7. </plugins>
  8. </build>

执行maven clean package之后,会生成两个文件:
image.png
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/

image.png
使用解压工具打开
image.png
打成的jar文件整体目录结构:

  1. ├─.idea
  2. │ └─libraries
  3. ├─BOOT-INF
  4. │ ├─classes
  5. │ │ └─com
  6. │ │ └─itmck
  7. │ │ └─springbootmq
  8. │ │ ├─compnent
  9. │ │ ├─config
  10. │ │ └─controller
  11. │ └─lib
  12. ├─META-INF
  13. │ └─maven
  14. │ └─com.itmck
  15. │ └─springboot-mq
  16. └─org
  17. ├─.idea
  18. └─springframework
  19. └─boot
  20. └─loader
  21. ├─archive
  22. ├─data
  23. ├─jar
  24. └─util

从MANIFEST.MF可以看到Main函数是JarLauncher,下面来分析它的工作流程。JarLauncher类的继承结构是:

class JarLauncher extends ExecutableArchiveLauncher
class ExecutableArchiveLauncher extends Launcher

  1. public class JarLauncher extends ExecutableArchiveLauncher {
  2. public JarLauncher() {}
  3. public static void main(String args[]) throws Exception {
  4. //其主入口新建了 JarLauncher 并调用父类 Launcher 中的launch方法启动程序
  5. (new JarLauncher()).launch(args);
  6. }
  7. }

通过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 {
    1. JarFile.registerUrlProtocolHandler();
    2. ClassLoader classLoader = createClassLoader(getClassPathArchives());//1
    3. launch(args, getMainClass(), classLoader);//2
    } }
  1. - 通过getMainClass() 可以找到最终获取到Manifest清单里的Start-Classvalue
  2. **com.itmck.springbootmq.SpringbootMqApplication**<br />**进而启动springbootmain方法**
  3. ```java
  4. public abstract class ExecutableArchiveLauncher extends Launcher {
  5. protected String getMainClass() throws Exception {
  6. Manifest manifest = this.archive.getManifest();//获取到清单
  7. String mainClass = null;
  8. if (manifest != null) {
  9. //清单存在就获取启动类
  10. mainClass = manifest.getMainAttributes().getValue("Start-Class");
  11. }
  12. if (mainClass == null) {
  13. throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
  14. } else {
  15. return mainClass;
  16. }
  17. }
  18. }

总结

  • 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()启动
    image.png
    找到通过run找到ConfigurableApplicationContext可以发现
    image.png
    找到通过ConfigurableApplicationContext找到SpringApplication 可以发现
    image.png
    总结:初始化信息
    image.png

通过ConfigurableApplicationContext找到run
image.png
可以看到如下:
image.png

SpringBoot启动事件

image.png

  • SpringApplicationRunListeners的唯一实现是EventPublishingRunListener;
  • 整个SpringBoot的启动,流程就是各种事件的发布,调用EventPublishingRunListener中的方法。

SpringBoot启动事件
image.png
创建和配置环境
image.png
准备ApplicationContext
image.png
发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
image.png
SpringBoot已启动事件
image.png
SpringBoot现在可以处理接受的请求事件
image.png

总结:

  • 初始化信息,包括web应用的类型获取,获取ApplicationListener与ApplicationContextInitializer
  • 获取运行监听器运行器,发布SpringBoot启动事件
  • 封装命令行参数,获取配置信息
  • 实例化上下文,准备上下文,发布EventPublishingRunListener事件
  • 刷新容器,加载ioc容器
  • 发布已启动事件监听