我们打包SpringBoot 项目时,需要使用打包的插件,此时会在打包的jar 文件下, 存在 /META-INF/MANIFEST.MF 文件,来看下这个文件的内容;
Main-class: 是java -jar 命令真正调用的类
Start-Class: 是由自己编写的SpringBoot入口类
有没有小伙伴疑惑,这里为什么不直接使用 我们自己编写的入口呢? 我把main-class 改成我们自己编写的入口后,再启动会提示 找不到类的异常;
我们改回原方式,然后打断点看看SpringBoot是怎么处理的;
这里教大家调试的流程;
1.使用插件打包我们的SpringBoot项目;下面是我的pom.xml
4.0.0
org.example
springboot
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.4.5
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-loader
org.springframework.boot
spring-boot-maven-plugin
repackage
2.idea工具配置jar application;
断点启动就可以调试了;
跟踪第一句 new JarLauncher(); 可以在其父类的构造参数中发现 this.archive = createArchive(); 内部的代码我没有一字一句的追,大体逻辑是 拿到我们运行的文件,判断是否为目录如果是目录 new ExplodedArchive(root) 如果不是 new JarFileArchive(root);
再往下跟lanuch()方法;发现创建了一个ClassLoader, 记得我们之前 直接启动自己编写的SpringBoot入口类时 提示类找不到; 当时的ClassLoader 为AppClassLoader, 默认加载ClassPath下的jar; 这里会不会是解决这个问题的呢? 我们主要追踪这部分代码; 跟进这个方法,查看一下构建ClassLoader时,传了哪些URL进去就可以了;
可以看到ClassLoader的类型是LaunchedURLClassLoader; 加载的是/BOOT-INF/classes 和 lib 下的文件;
我在自己编写的SpringBoot入口方法中,输出了当前的ClassLoader 我们观察一下,通过直接启动入口类 和 java -jar 启动输出的ClassLoader有什么不同;
1.通过IDEA 直接启动入口类,此时我的jar包 及 class文件都在ClassPath下;
2.通过java -jar命令启动; 可以发现两种启动方式 ClassLoader不同;
我们继续跟进 String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass(); 获得入口类;
可以看到getMainClass 就是获取了MANIFEST.MF 文件中 Start-Class 的值;
最后通过反射调用,开始启动SpringBoot
比较重要的几点:
1.根据当前的jar文件,判断是webmvc 或 webflux环境,启动不同的spring容器实例
2.加载META-INF/spring.factories 下的文件, 名字为 org.springframework.context.ApplicationContextInitializer 实现 ApplicationContextInitializer接口
3.加载META-INF/spring.factories 下的文件, 名字为 org.springframework.context.ApplicationListener 实现 ApplicationListener接口
1.创建SpringApplicationRunListeners 对象,整个SpringBoot生命周期使用事件进行管理; 默认实现类为EventPublishingRunListener; 我们可以通过 META-INF/spring.factories
定义org.springframework.context.ApplicationListener=com.jtfu.boot.study.enchane.MyStartingListener 的方式来增强;
2.创建Spring容器, 根据前面解析的WebMCV 或者 WebFlux
3.初始化Spring容器参数,加载配置类
4.执行Spring容器的生命周期函数;
1.去 META-INF/spring.factories 查找名字为 org.springframework.boot.SpringApplicationRunListener 的实现类, Spring内置了一个. EventPublishingRunListener
EventPublishingRunListener 中的各个方法 会在SpringBoot 生命周期中被调用;
2.根据类型创建不同的Spring容器上下文
3.初始化Spring容器上下文的属性; 调用实现了ApplicationContextInitializer接口的方法, 发布contextPrepared事件,加载启动类,发布contextLoad事件
4.执行Spring容器的实例化函数,一路调用就会到了 Spring容器中最终的方法里面; refresh(); 这部分逻辑属于Spring源码;
5.发布started 事件
6.发布running 事件 完成Springboot 启动Spring容器上下文
经过前面分析可以知道Spring容器上下文的类型为 AnnotationConfigServletWebServerApplicationContext, 在执行Spring 的启动过程中 会调用 onRefresh()方法;
在这里完成了tomcat的启动 和 dispatchServlet ,Listener 的配置;
1.修改pom.xml 文件 打包方式改为war
2.排除web包下的内嵌tomcat
3.继承SpringBootServletInitializer,重写configure()方法;
4.打包完成后idea 启动tomcat 应用;
tomcat 启动时基于servlet 3.0规范,去META-INF/services ,查找文件 javax.servlet.ServletContainerInitializer (这是个文件名,spi机制), 文件中有一个实现类,tomcat启动时就会调用它的 onStartup()方法;
能拿到我们自己实现接口的类,并调用它的onStartup()方法;
调用这个方法时,逻辑跟内嵌tomcat 启动 差不多,大家自行跟进理解一下吧.