一、简述
Maven项目通过package打成jar包后,jar包中包含所有依赖lib文件。本文介绍了两种方式解析嵌套jar中的class文件,一种是通过spring-boot-loader包JarFileArchive,另一种是util包中JarFile。
二、JarFileArchive方式
1.spring-boot-loader依赖引入
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
2.demo案例
java
public static void main(String args[]) throws Exception {
String jarPath = "C:\\Users\\root\\Desktop\\make-test.jar";
// 方案一:spring-boot-loader
long start1 = System.currentTimeMillis();
getClassInfoByJarLib(jarPath);
long end1 = System.currentTimeMillis();
log.info("收集所有lib类ClassInfo,花费时间={}",(end1-start1));
}
public static void getClassInfoByJarLib(String jarPath) {
String filePath = "file:/"+ URLDecoder.decode(jarPath, StandardCharsets.UTF_8).replaceAll("\\\\","/")+"!/";
String rootJarPath = "jar:"+ filePath;
try {
JarFileArchive jarFileArchive = new JarFileArchive(new Handler().getRootJarFileFromUrl(new URL(rootJarPath)));
//getNestedArchives获取嵌套的jar等文件,参数是个EntryFilter,过滤条件
jarFileArchive.getNestedArchives(entry -> entry.getName().startsWith("BOOT-INF/lib/") && entry.getName().endsWith(".jar"))
.forEach(archive -> {
archive.iterator().forEachRemaining(entry -> {
String entryName = entry.getName();
// 过滤嵌套jar包中字节码文件
if (entryName.endsWith(".class")) {
String className = entryName.replace('/', '.').replace(".class", "");
log.info("className:{}",className);
}
});
});
} catch (IOException e) {
log.error("解析嵌套jarLib中ClassInfo异常,jarPath={}",jarPath,e);
throw new RuntimeException(e);
}
}
三、JarFile方式
1.demo案例
java
public static void main(String args[]) throws Exception {
String jarPath = "C:\\Users\\root\\Desktop\\make-test.jar";
// 方案二:JarFile
long start2 = System.currentTimeMillis();
processJar(jarPath);
long end2 = System.currentTimeMillis();
log.info("收集所有lib类ClassInfo,花费时间={}",(end2-start2));
}
private static void processJar(String jarPath){
try (JarFile jarFile = new JarFile(new File(jarPath))) {
jarFile.stream().parallel()
// 过滤出所有符合要求的jar包
.filter(entry -> !entry.isDirectory() && entry.getName().startsWith("BOOT-INF/lib/") && entry.getName().endsWith(".jar"))
.forEach(entry -> processNestedJar(jarFile, entry.getName()));
} catch (IOException e) {
log.error("解析嵌套jarLib中ClassInfo异常,jarPath={}",jarPath,e);
throw new RuntimeException(e);
}
}
private static void processNestedJar(JarFile jarFile, String entryName){
// 处理嵌套jar文件
try (InputStream nestedJarStream = jarFile.getInputStream(jarFile.getJarEntry(entryName));
JarInputStream jarInputStream = new JarInputStream(nestedJarStream)) {
JarEntry nestedEntry;
while ((nestedEntry = jarInputStream.getNextJarEntry()) != null) {
if (nestedEntry.isDirectory()) {
continue;
}
String nestedEntryName = nestedEntry.getName();
if (!nestedEntryName.endsWith(".class")) {
continue;
}
try {
String className = nestedEntryName.replace('/', '.').replace(".class", "");
log.info("className:{}",className);
} catch (Exception e) {
log.error("目标类={}查找失败",nestedEntryName,e);
throw new RuntimeException(e);
}
}
} catch (IOException e) {
log.error("目标类={}查找失败",entryName,e);
throw new RuntimeException(e);
}
}
四、两种方式对比
实测项目make-test.jar中所有依赖lib约200个,其中所有class字节码文件约7万多个。方案JarFileArchive约1.5s全部解析,方案JarFile约6s全部解析。