SpringBoot-Jar包启动流程

Spring Boot Jar包启动流程

启动流程概览

  1. 首先,我们通过java -jar命令启动jar包,此时调用Launcher$AppClassLoader类加载器加载,此时涉及了MANIFEST.MF中Main-Class对应的属性

  2. 然后构建Spring Boot加载器,通过调用LaunchedURLClassLoader类加载器加载jar包中/BOOT-INF/classes/和/BOOT-INF/lib/*.jar文件

  3. 基于反射调用应用程序的启动方法,此时涉及了MANIFEST.MF中的Start-Class对应的属性

    image-20210409203550480

Jar包结构

首先我们来看下jar包的结构:

spring-boot的jar包结构

  • 其中BOOT-INF就是我们自己写的应用的Class,包括:
    • classes:我们在spring boot项目中实现的业务对象
    • lib:存放的就是spring boot项目中的maven依赖,就是一个个的jar包
  • META-INF:
    • maven:包含了基本的maven配置文件
    • MANIFEST.MF:在spring boot的jar包中,这个文件是不能缺少的,否则就无法正常启动
  • org.springframework.boot.loader是来自于spring-boot-loader.jar包的文件,这里为什么不把这个jar包放到lib目录下,而是将它的文件拷贝出来单独放在一个文件夹下呢?这个留待后面解答

MANIFEST.MF文件

首先我们看看文件的内容:

image-20210409161456613

该文件中最重要的就是Main-Class和Start-Class这两个属性对应的启动类。

Start-Class是我们真正要启动的类,该文件存储在BOOT-INF/classes目录下。

从这里我们可以解答刚开始的那个问题:为什么将spring-boot-loader.jar文件的内容单独拷贝出来;这是因为我们在MANIFEST.MF中配置了Main-Class这个属性,那么当jar包启动的时候,就必须能够找到这个类,如果是在lib下的话,则启动时无法加载到这个类,所以这里将这些文件直接拷贝出来丢到jar包里面,让JarLuncher在启动jar包时可以直接启动。

我们来看看这个JarLauncher在哪里:

image-20210409162350356

Jar中不同类对应的类加载器

image-20210409164403391

spring boot加载器能直接加载BOOT-INF/*下的jar包和类吗?

jdk自己实现的协议无法加载jar包中的jar包,我们通过如下程序测试下:

	@Test
	public void test() throws IOException, ClassNotFoundException {
        // 这里的地址替换为自己本地jar包的位置运行即可,spring-boot的jar包基本都会包含这个spring-beans的jar包
		URL nested_url = new URL("jar:file:///D:/spring_project/myblog/target/demo-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/spring-beans-5.2.7.RELEASE.jar");
        // 这里没有加载嵌套的jar包,只加载了我们自己打包的文件
		URL url = new URL("file:///D:/spring_project/myblog/target/demo-0.0.1-SNAPSHOT.jar");
        
		URLClassLoader loader = new URLClassLoader(new URL[]{nested_url, url}, null);
		Class<?> aClass = loader.loadClass("org.springframework.boot.loader.JarLauncher");
		System.out.println(aClass);
        // 由于无法加载jar中jar所以这里会报错!具体情况请看下图
		loader.loadClass("org.springframework.beans.BeanUtils");
	}

image-20210409192101407

那么Spring Boot到底如何解决这个问题呢?

首先,我们来看看通过URL协议我们是如何加载这些文件的。对于每种协议(包括file、ftp、http、jar等),jdk都提供了对应的Handler实现类,如图所示:

image-20210409193708744

既然通过这些Handler无法读取jar里面的jar文件,那么spring boot的开发者就另辟蹊径,替换掉了读取jar的Handler。首先来看看Java中URL包含协议的请求流程:

image-20210409194357228

java的URL类中,会根据协议的名称进行包名的拼接,然后选择对应的Handler,我们可以在URL源码中看到这一点:

image-20210409195321577

覆盖jar中的Handler有三种方法:

  1. 指定URL HandlerFactory
  2. 修改默认匹配包名
  3. 采用默认包名

我们可以设置URLStreamHandlerFactory来替换Handler,但是这个URLStreamHandlerFactory只能被替换一次。

image-20210409195456002

正常情况下,这是没问题的,但是我们使用tomcat启动的时候就会报错,因为tomcat已经设置过URLStreamHandlerFactory一次,所以spring boot如果再次设置的话还是会报错。但是spring boot的还是有自己的办法解决这个问题,我们看看它是如何做的。

此时我们又要回到MANIFEST.MF文件,看看这个Main-Class对应的org.springframework.boot.loader.JarLauncher的main启动方法:

image-20210409200544649

image-20210409200614953

image-20210409200632036

最终其实我们可以发现,spring-boot通过System.getProperty方法设定了默认包名的路径!

我们可以在刚才失败的方法上面添加这个方法,看是否能成功。此时还要修改以下jar协议,要在jar的后面加上"!/",那么新的测试类如下:

	@Test
	public void test() throws IOException, ClassNotFoundException {
        
		JarFile.registerUrlProtocolHandler();

		// 加载jar中jar
		URL nested_url = new URL("jar:file:///D:/spring_project/myblog/target/demo-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/spring-beans-5.2.7.RELEASE.jar!/");
		URL url = new URL("file:///D:/spring_project/myblog/target/demo-0.0.1-SNAPSHOT.jar");
		URLClassLoader loader = new URLClassLoader(new URL[]{nested_url, url}, null);
		Class<?> aClass = loader.loadClass("org.springframework.boot.loader.JarLauncher");
		System.out.println(aClass);
		loader.loadClass("org.springframework.beans.BeanUtils");
	}

此时运行就不会报错了!

我们可以调试,看看不调用JarFile.registerUrlProtocolHandler方法与调用方法对应的Handler

image-20210409202712685

执行JarFile.registerUrlProtocolHandler方法后:

image-20210409202800042

可以看到两者的区别,他们对应的handler不同,一个是java底层的,一个是spring-boot-loader自定义的。

此时我们就了解了为什么spring能加载jar中jar了!

继续spring的jar包启动原理!

基于反射调用Start-Class

接下来就要讲第三步通过反射找到Start-Class并调用。

image-20210409205358754

这里最终没搞好调试的环境,所以直接点进源码看了:

image-20210409212705763

image-20210409213156358

image-20210409213311590

image-20210409213428976

image-20210409213522535

调用到这里就会执行我们启动类的方法。

接下来我们看看启动方法调用链:

image-20210409214645157

参考

鲁班大叔

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个开源的框架,用于构建独立的、可执行的Spring应用程序。在使用Spring Boot构建的应用程序中,可以使用以下步骤启动应用程序。 1. 创建一个Spring Boot项目:首先,需要创建一个基于Spring Boot的Maven或Gradle项目。可以使用Spring Initializr来生成项目的基本结构和依赖关系。 2. 配置应用程序:在项目中,可以使用application.properties或application.yml文件来配置应用程序的属性,如数据库连接、服务器端口等。 3. 编写应用程序代码:编写应用程序的主要业务逻辑,包括控制器、服务、存储库等。Spring Boot提供了大量的自动配置功能,可以简化开发过程。 4. 构建应用程序:使用Maven或Gradle构建项目,生成可执行的jar文件。 5. 启动应用程序:使用java命令或者使用IDE中的运行按钮来启动应用程序。执行命令java -jar your-app.jar来启动应用程序。 6. 应用程序初始化:当应用程序启动时,Spring Boot会执行一些初始化操作,例如扫描注解、创建Bean、配置日志等。 7. 自动配置:Spring Boot根据应用程序的配置和依赖关系自动配置应用程序,例如数据库连接、Web服务器等。 8. 创建服务器:Spring Boot会创建一个嵌入式的服务器,例如Tomcat或Jetty,用于处理HTTP请求。 9. 处理请求:当收到HTTP请求时,服务器会将请求转发给相应的控制器类,并执行相应的处理方法。 10. 返回响应:控制器方法处理完请求后,会将处理结果封装为HTTP响应,并返回给客户端。 总之,Spring Boot启动流程可以简单概括为创建项目、配置应用程序、编写代码、构建项目、启动应用程序、初始化应用程序、自动配置应用程序、创建服务器、处理请求和返回响应。通过这个流程,可以快速开发并部署基于Spring Boot的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值