在jar包中引用其他jar包

60

我有一个罐子,其内容如下所示:

图片描述

以下是我的清单文件。

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.3
Created-By: 1.7.0_06-b24 (Oracle Corporation)
Main-Class: org.sai.com.DerbyDemo
Class-Path: derby.jar derbyclient.jar derbynet.jar derbytools.jar
当我尝试运行这个jar文件时,它抛出了一个ClassNotFoundExcception异常,意味着它没有引用外部jar里面的库。在Class-Path属性中,我该如何引用实际jar包内部的库(例如derby.jar)?

可能是Classpath including JAR within a JAR的重复问题。 - Julien Kronegg
7个回答

40
你需要一个自定义类加载器才能实现此功能,可以看一下One Jar。它可以将Java应用程序及其所依赖的Jars打包成单个可执行Jar文件,还有一个ant任务可简化构建过程。 参考资料(来自背景介绍): 大多数开发人员合理地认为将依赖的Jar文件放入自己的Jar文件中,并在META-INF/MANIFEST中添加Class-Path属性就可以解决问题:

jarname.jar
| /META-INF
| |  MANIFEST.MF
| |    Main-Class: com.mydomain.mypackage.Main
| |    Class-Path: commons-logging.jar
| /com/mydomain/mypackage
| |  Main.class
| commons-logging.jar

很遗憾这个方法不起作用。Java的Launcher$AppClassLoader无法使用这种Class-Path加载Jar内部的Jar。尝试使用jar:file:jarname.jar!/commons-logging.jar也是行不通的。只有将支持的Jar文件安装(即分散)到安装jarname.jar文件的目录中才能使用此方法。


24
你不能这样做。根据官方教程,通过在清单文件中使用Class-Path头部,您可以避免在运行Java应用程序时指定长的-classpath标志。
注意:Class-Path头部指向本地网络上的类或JAR文件,而不是JAR文件内部或通过Internet协议可访问的类。要将JAR文件内的类加载到类路径中,您必须编写自定义代码来加载那些类。例如,如果MyJar.jar包含一个名为MyUtils.jar的另一个JAR文件,则无法使用MyJar.jar的清单中的Class-Path头部将MyUtils.jar中的类加载到类路径中。

17
在Eclipse中,您可以选择导出可执行的jar文件。 您有将所有与项目相关的jar包打包到生成的jar包中的选项,这样Eclipse会添加自定义类加载器,该加载器将在新的jar包中引用您集成的jar包。
其中第一张图片演示了如何在Eclipse中导出可执行的jar文件,第二张图片则展示了如何将项目中涉及到的所有jar包打包到生成的jar包中。

1
@vogash,你是如何创建启动配置MainActivity-asc_validation的?在我的情况下,我有一个Android应用程序,我将其作为jar文件使用。这个jar文件又引用了多个其他的jar文件。所以我尝试使用这个选项。但是我卡在这一点上了。我无法将Android配置获取到启动配置中。 - sangram
太好了!这个完美地运行了,我不明白为什么它的文档不更好!非常感谢你,Vogash! - jfajunior
真的很有效。我一直在做各种命令行操作,却不知道Eclipse也有这个功能...谢谢分享。 - Peter
为了使其正常工作,重要的是在导出时选择“可运行的JAR文件”(如上面的截图所示),而不是“JAR文件”。 - dan

6

类加载器的默认实现无法从jar-within-a-jar中加载:为了这样做,整个 "子jar" 必须被加载到内存中,这将破坏jar格式的随机访问优势(参考待定 - 一旦我找到支持此操作的文档,我会进行编辑)。

我建议使用像 JarSplice 这样的程序,将所有内容捆绑成一个干净的可执行jar文件。

编辑:找不到源引用,但是在Sun网站上有一个未解决的RFE描述了这个确切的问题:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4648386

此外,您可以通过将库jar文件放置在\lib子目录中的方式来 "测试" 您的程序是否有效,然后从命令行运行。换句话说,具有以下目录结构:

classes/org/sai/com/DerbyDemo.class
classes/org/sai/com/OtherClassFiles.class
classes/lib/derby.jar
classes/lib/derbyclient.jar

从命令行中,导航到上述“classes”目录,并输入以下命令:
java -cp .:lib/* org.sai.com.DerbyDemo

4

如果您不想创建自定义的类加载器,您可以读取JAR文件流并将其转换为File对象。然后,您可以获取该文件的URL,并将其发送到URLClassLoader中,以按照所需方式加载JAR文件。

InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("example"+ ".jar");
final File tempFile = File.createTempFile("temp", ".jar");
tempFile.deleteOnExit();  // you can delete the temp file or not
try (FileOutputStream out = new FileOutputStream(tempFile)) {
    IOUtils.copy(resourceAsStream, out);
}
IOUtils.closeQuietly(resourceAsStream);
URL url = tempFile.toURI().toURL();
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
urlClassLoader.loadClass()
...

2

如果使用NetBeans,将相应的JAR文件添加到您的中,并按以下方式修改您的清单文件的classpath

Class-Path: lib/derby.jar lib/derbyclient.jar lib/derbynet.jar lib/derbytools.jar

这里有一个类似的答案:这里


-1
在Eclipse中,右键单击项目,选择“Run As” -> “Run Configuration”,保存您的运行配置,在下一次导出可运行JAR时将使用此配置。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接