2023年12月2日发(作者:)

读取jar包中嵌套的jar包内容的方法背景最近在做 javaagent 的时候,我们需要将很多依赖的包打成一个大大的 jar 包,这时候可以用maven-shade-plugin 进行操作,但是如果我们的代码不想默认被 AppClassloader 来加载(javaagent 的代码默认是由 AppClassloader 来进行加载的),又不想将这些包放在这个大 jar 包的外面,这个时候我们就需要吧这些代码以 jar 包的形式放在 resources 里面,最终 jar 包图可能是这样.── org ├── ── plugins ├── ├── ├── ...复制代码org 文件夹里面存放的是我们编译后的.class 文件, plugins 存放的是一些需要额外加载的 jar 包,默认情况下,里面的代码是当前classloader 加载不到的,需要自定义 classloader 来加载.如何加载但是如何来加载jar 包里面的文件呢?假如外面这层 jar 包的名字为. 你可能会自定义一个 classloader, 加入有个 位于 中,自定义 classloader 当然需要覆写 findClass 方法,如何把这个文件加载到内存呢?思路1我们都知道对于读取 jar 包里面的路径都有特定的格式,比如读取 的 jarEntry可以这样读取 JarFile jarFile = new JarFile(new File("")); Enumeration entries = s(); while (eElements()) { JarEntry jarEntry = ement(); String name = e(); if("plugins/".equals(name)){ ..... } }复制代码这样我们可以拿到这个 jar 包对应的 jarEntry, 但是拿到之后好像并不能干啥,也没有方法把他当成一个 jar file 继续获取里面的类.所以这种方式暂时不可行思路2直接用 URL 获取路径,比如获取 URL url = new URL("jar:file:","",-1,"!/plugins/"); utstream();....复制代码这样貌似可以将一个 jar 获取为一个 inputstream, 但是 里面的类怎么获取呢?获取你会想这样 URL url = new URL("jar:file:","",-1,"!/plugins/!/");nnection();复制代码但是好像是不行的.思路3既然读取 jar 包里面的内可以用!/这样的格式,那么读取一层 jar 包应该是没有问题的,如果我们可以在运行前将 jar 包中的 jar 文件解压出来,放在一个目录,那么就有办法读取其中的内容了,所以我们的思路是:解压需要读取的嵌套 jar 包文件到一个一个临时的文件夹,并且每次解压要唯一通过临时文件夹读取其中的类,加载到类加载器.JVM 退出的时候删除这个临时文件夹,避免无谓的存储消耗.按照这样的思路,于是有了下面的方法:获取临时目录 if (TEMP_FOLDER == null) { synchronized () { if (TEMP_FOLDER == null) { TEMP_FOLDER = unpackToFolder(jarPath); } } }复制代码//需要的 jar 包解压到文件夹private File unpackToFolder(File jarPath) { try { File tempFolder = new File(perty("")); File folder = new File(tempFolder, "test-loader-" + UUID()); File pluginsFolder = new File(folder, "plugins"); if (!() || !()) { ("cannot makedir temp dir"); throw new RuntimeException("can not mkdir temp dir"); } OnExit(); OnExit(); (" temp folder is {}",onicalPath()); JarFile jarFile = new JarFile(jarPath); Enumeration entries = s(); while (eElements()) { JarEntry jarEntry = ement(); String name = e(); String[] split = ("/"); if (With("plugins/") && > 1) { File file = new File(pluginsFolder, split[1]); unpack(jarFile, jarEntry, file); OnExit(); }

} return folder; } catch (Exception e) { (" unpack to folder error", e); throw new RuntimeException(e); } }// 解压 jar 包; private static void unpack(JarFile jarFile, JarEntry entry, File file) throws IOException { try (InputStream inputStream = utStream(entry)) { try (OutputStream outputStream = new FileOutputStream(file)) { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = (buffer)) != -1) { (buffer, 0, bytesRead); } (); } } }复制代码其实我们最终获取到TEMP_FOLDER其他操作都像读文件一样了,关键在于如何解压,这里面的几个小细节:OnExit(); 的使用,相当于给文件删除注册了一个钩子,当 JVM 退出的时候,自动回删除这个文件,最终被删除的文件是保存在一个队列里面的,所以这里的删除代码顺序注册也是有讲究的.每次创建的文件夹都不一样,避免污染环境,读取的文件过多或者过少.直接读取虽然不能随意读取嵌套jar 包中的内容,但是JarFileEntry 中可以读取manifest 文件,我们可以一些需要读取的放在这个文件里面,然后在外面直接读取.public synchronized Manifest getManifest() throws IOException {}复制代码引用ps: 除了临时目录的方法,可能还有更好的方法.