优秀的编程知识分享平台

网站首页 > 技术文章 正文

Jar启动和IDE里启动Sprintboot的区别

nanyue 2024-07-29 01:18:10 技术文章 9 ℃

想聊明白这个问题,需要补充一些前提条件,比如Fat jar、类加载机制等

1、Fat jar

我们在开发业务程序的时候,经常需要引用第三方的jar包,最终程序开发完成之后,通过打包程序,会把自己的代码和三方jar包一起打成同一个jar包,这种jar就称之为Fat jar

SpringBoot的maven插件,打包方式就是把整体项目打包成一个Fat jar,其中把应用程序代码打包到BOOT-INF/classes中,把三方jar包打包到BOOT-INF/lib中,把jar包的详细信息(启动入口等)放置在META-INF/MANIFEST.MF文件中。

 <plugins>
     <plugin>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-maven-plugin</artifactId>
         <configuration>
             <mainClass>xxxxApplication</mainClass>
         </configuration>
     </plugin>
 </plugins>

整体Fat jar如图所示:


2、jar启动入口

如果一个jar内部有多个类都有main方法,那么java -jar启动的话会调用哪个main方法呢,换句话说就是一个jar启动的入口main方法是怎么定义的?

两种方式:

  • 命令行直接指定,java -jar xxx.jar com.xx.xxxClass(不常用)
  • maven打包的时候插件生成META-INF/MANIFEST.MF文件,文件中的Main-Class:后面定义了当前jar的启动入口

那么SpringBoot启动的入口是我们应用程序里面写得加了@SpringBootApplication的main方法吗? 显然不是,打开jar查看MANIFEST.MF文件,里面赫然写着Main-Class: org.springframework.boot.loader.JarLauncher

所以说,通过java -jar 启动一个Springboot项目,启动的入口是 org.springframework.boot.loader.JarLauncher类里面的main方法。

3、类加载

根据刚才看到的jar内部的目录结构,应用程序依赖的三方jar包都不在classpath目录下,按照已有的类加载器的职能来看,这些jar都不能被加载到JVM中,SpringBoot需要自定义类加载器来加载这些包,具体是怎么做的,还是那句话,源码之下无秘密。打开JarLauncher类,如图所示:

main方法很简单,就一行代码, (new JarLauncher()).launch(args); 跟代码进入launch方法。

由代码得知,SpringBoot是自定义了一个LaunchedURLClassLoader来加载SpringBoot应用的所有类

这个结论也可以程序实现得知:IDEA启动输出类加载和jar启动输出类加载器 ,结果一目了然

4、结论

Java -Jar是以FAT JAR的方式用LaunchedURLClassLoader来load class。而在IDE中则是直接以ApplicationClassLoader来load的。这种差别会导致调用classloader.getResourceAsStream()得到不一样的结果,这是因为FAT JAR启动时,LaunchedURLClassLoader的load的urls并没有FAT JAR本身,如abc-0.0.1-SNAPSHOT.jar, 但是应用中的src/main/resources/META-INF/resources目录被打包到了FAT JAR里,也就是abc-0.0.1-SNAPSHOT.jar!/META-INF/resources,这样这些resource也就不会被访问到了。

这也就是为什么有时候在IDE里能读到的resource在Run FAT JAR的情况下读不到了

Tags:

最近发表
标签列表