问题
上篇写了获取jar包中的class文件,在IDEA运行的好好的, 打个包丢服务器上就跑不了了。 原因就是打成jar包之后获取到的class文件路径变成/demo/xxx.jar!/WEB-INF/lib/yyy.jar!/com/demo/zzz.class了,在IDEA中只要读取yyy.jar中的class就行,现在要读取xxx.jar包中WEB-INF/lib下yyy.jar中的class文件, 所以今天再来说说获取jar包中的jar包中的class文件。
分析
一开始的想法是读取到的文件如果是jar包就再读取这个jar包中的文件,可是发现JarFile和JarEntry中并没有方法能返回jar文件,也不能获取内层文件。Url也不能直接获取两层jar包。
那么我们打的jar包在服务器使用java -jar启动的时候是这么读取jar包内的文件呢。来解压jar包查看MANIFEST.MF文件:
Manifest-Version: 1.0
Implementation-Title: application
Implementation-Version: 2.0-SNAPSHOT
Start-Class: com.demo.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.4.RELEASE
Created-By: Maven Archiver 3.4.0
Implementation-Vendor: Yuanting information technology co.,LTD
Main-Class: org.springframework.boot.loader.JarLauncher
看这个Main-Class入口类是org.springframework.boot.loader.JarLauncher,执行流程大致如下,有兴趣的可以深入了解一下。
解决
复制粘贴工程师就不了解那么多了,直接看解决方案。
首先添加org.springframework.boot.loader依赖:
<!-- spring-boot-loader-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
精华就在于重写的JarFile,用它读取jar文件就行了:
public void testJarLaunch() throws IOException, ClassNotFoundException {
String path = "jar:file:/IdeaProjects/demo-application/target/demo-application-1.0-SNAPSHOT.jar!";
URL url = new URL(path);
Handler handler = new Handler();
org.springframework.boot.loader.jar.JarFile root = handler.getRootJarFileFromUrl(url);
JarFileArchive archive = new JarFileArchive(root);
//灵魂方法getNestedArchives获取嵌套的jar等文件,参数是个EntryFilter,过滤条件
List<Archive> list = archive.getNestedArchives(entry -> {
System.out.println(entry.getName());
return entry.getName().endsWith("BOOT-INF/lib/app-core-1.0-SNAPSHOT.jar");
});
//list里就是每个jar了
for (Archive entries : list) {
//获取jar里的内容
Iterator<Archive.Entry> it = entries.iterator();
while (it.hasNext()){
Archive.Entry entry = it.next();
String name = entry.getName();
System.out.println(name);
}
}
}
懒得说了有兴趣的参考这篇:SpringBoot的main函数运行之前都发生了什么