使用 Ant 在新的 jar 文件构建中包含外部 jar 文件
Posted
技术标签:
【中文标题】使用 Ant 在新的 jar 文件构建中包含外部 jar 文件【英文标题】:Including external jar-files in a new jar-file build with Ant 【发布时间】:2011-04-15 18:22:50 【问题描述】:我刚刚“继承”了一个 Java 项目,而不是来自 Java 背景,有时我有点迷茫。 Eclipse 用于在开发过程中调试和运行应用程序。我通过 Eclipse 成功创建了一个 .jar 文件,该文件“包含”所有必需的外部 jar,如 Log4J、xmlrpc-server 等。然后可以使用以下方法成功运行这个大 .jar:
java -jar myjar.jar
我的下一步是使用 Ant(版本 1.7.1)自动化构建,因此我不必让 Eclipse 来进行构建和部署。由于我缺乏 Java 知识,这已被证明是一个挑战。项目的根目录如下所示:
|-> jars (where external jars have been placed)
|-> java
| |-> bin (where the finished .class / .jars are placed)
| |-> src (Where code lives)
| |-> ++files like build.xml etc
|-> sql (you guessed it; sql! )
我的 build.xml 包含以下内容:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project basedir="." default="build" name="Seraph">
<property environment="env"/>
<property name="debuglevel" value="source,lines,vars"/>
<property name="target" value="1.6"/>
<property name="source" value="1.6"/>
<property name="build.dir" value="bin"/>
<property name="src.dir" value="src"/>
<property name="lib.dir" value="../jars"/>
<property name="classes.dir" value="$build.dir/classes"/>
<property name="jar.dir" value="$build.dir/jar"/>
<property name="jar.file" value="$jar.dir/seraph.jar"/>
<property name="manifest.file" value="$jar.dir/MANIFEST.MF"/>
<property name="main.class" value="no.easyconnect.seraph.core.SeraphCore"/>
<path id="external.jars">
<fileset dir="$lib.dir" includes="**/*.jar"/>
</path>
<path id="project.classpath">
<pathelement location="$src.dir"/>
<path refid="external.jars" />
</path>
<target name="init">
<mkdir dir="$build.dir"/>
<mkdir dir="$classes.dir"/>
<mkdir dir="$jar.dir"/>
<copy includeemptydirs="false" todir="$build.dir">
<fileset dir="$src.dir">
<exclude name="**/*.launch"/>
<exclude name="**/*.java"/>
</fileset>
</copy>
</target>
<target name="clean">
<delete dir="$build.dir"/>
</target>
<target name="cleanall" depends="clean"/>
<target name="build" depends="init">
<echo message="$ant.project.name: $ant.file"/>
<javac debug="true" debuglevel="$debuglevel" destdir="bin" source="$source" target="$target" classpathref="project.classpath">
<src path="$src.dir"/>
</javac>
</target>
<target name="build-jar" depends="build">
<delete file="$jar.file" />
<delete file="$manifest.file" />
<manifest file="$manifest.file" >
<attribute name="built-by" value="$user.name" />
<attribute name="Main-Class" value="$main.class" />
</manifest>
<jar destfile="$jar.file"
basedir="$build.dir"
manifest="$manifest.file">
<fileset dir="$classes.dir" includes="**/*.class" />
<fileset dir="$lib.dir" includes="**/*.jar" />
</jar>
</target>
</project>
然后我运行: ant clean build-jar
一个名为 seraph.jar 的文件被放置在 java/bin/jar 目录中。然后我尝试使用以下命令运行这个 jar: java -jar bin/jar/seraph.jar
结果是控制台的输出:
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/Logger
at no.easyconnect.seraph.core.SeraphCore.<clinit>(SeraphCore.java:23)
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Logger
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
... 1 more
Could not find the main class: no.easyconnect.seraph.core.SeraphCore. Program will exit.
我怀疑我在 build.xml 文件中做了一些非常愚蠢的事情,并且花了两天的大部分时间尝试更改配置,但无济于事。非常感谢您对这项工作的任何帮助。
哦,如果我遗漏了一些重要信息,我很抱歉。这是我第一次在 SO 发帖。
【问题讨论】:
如果我是你,我会使用 Maven 2 或 Maven 3 而不是 Ant,但这也是一个挑战。 我确实设法找到了一个好的方法来完成我想要的。详情见下文。 【参考方案1】:根据您的 ant 构建文件,我假设您想要创建一个 JAR 存档,其中不仅包含您的应用程序类,还包含您的应用程序所需的其他 JAR 的内容。
但是,您的 build-jar
文件只是将所需的 JAR 放入您自己的 JAR 中;这不会像here 解释的那样工作(见注释)。
尝试修改这个:
<jar destfile="$jar.file"
basedir="$build.dir"
manifest="$manifest.file">
<fileset dir="$classes.dir" includes="**/*.class" />
<fileset dir="$lib.dir" includes="**/*.jar" />
</jar>
到这里:
<jar destfile="$jar.file"
basedir="$build.dir"
manifest="$manifest.file">
<fileset dir="$classes.dir" includes="**/*.class" />
<zipgroupfileset dir="$lib.dir" includes="**/*.jar" />
</jar>
更灵活、更强大的解决方案是JarJar 或One-Jar 项目。如果以上内容不满足您的要求,请查看这些内容。
【讨论】:
这是否会提取 jar 并将类文件包含在最终 jar 中。它在我的构建文件中表现出相同的行为。我已经发布了我的问题here。【参考方案2】:根据在这里回答的人的有用建议,我开始深入研究 One-Jar。经过一些死胡同(以及一些与我之前的结果完全一样的结果,我设法让它工作。为了其他人的参考,我列出了对我有用的 build.xml。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project basedir="." default="build" name="<INSERT_PROJECT_NAME_HERE>">
<property environment="env"/>
<property name="debuglevel" value="source,lines,vars"/>
<property name="target" value="1.6"/>
<property name="source" value="1.6"/>
<property name="one-jar.dist.dir" value="../onejar"/>
<import file="$one-jar.dist.dir/one-jar-ant-task.xml" optional="true" />
<property name="src.dir" value="src"/>
<property name="bin.dir" value="bin"/>
<property name="build.dir" value="build"/>
<property name="classes.dir" value="$build.dir/classes"/>
<property name="jar.target.dir" value="$build.dir/jars"/>
<property name="external.lib.dir" value="../jars"/>
<property name="final.jar" value="$bin.dir/<INSERT_NAME_OF_FINAL_JAR_HERE>"/>
<property name="main.class" value="<INSERT_MAIN_CLASS_HERE>"/>
<path id="project.classpath">
<fileset dir="$external.lib.dir">
<include name="*.jar"/>
</fileset>
</path>
<target name="init">
<mkdir dir="$bin.dir"/>
<mkdir dir="$build.dir"/>
<mkdir dir="$classes.dir"/>
<mkdir dir="$jar.target.dir"/>
<copy includeemptydirs="false" todir="$classes.dir">
<fileset dir="$src.dir">
<exclude name="**/*.launch"/>
<exclude name="**/*.java"/>
</fileset>
</copy>
</target>
<target name="clean">
<delete dir="$build.dir"/>
<delete dir="$bin.dir"/>
</target>
<target name="cleanall" depends="clean"/>
<target name="build" depends="init">
<echo message="$ant.project.name: $ant.file"/>
<javac debug="true" debuglevel="$debuglevel" destdir="$classes.dir" source="$source" target="$target">
<src path="$src.dir"/>
<classpath refid="project.classpath"/>
</javac>
</target>
<target name="build-jar" depends="build">
<delete file="$final.jar" />
<one-jar destfile="$final.jar" onejarmainclass="$main.class">
<main>
<fileset dir="$classes.dir"/>
</main>
<lib>
<fileset dir="$external.lib.dir" />
</lib>
</one-jar>
</target>
</project>
我希望其他人可以从中受益。
【讨论】:
嗨,老答案,我知道,但是清单应该如何查找您的示例?编辑:知道了,one-jar 自己构建。【参考方案3】:Cheesle 是对的。类加载器无法找到嵌入的 jar。如果您在命令行上放置了足够多的调试命令,您应该能够看到“java”命令无法将 jars 添加到类路径
您想要制作的东西有时被称为“uberjar”。我发现one-jar 是帮助制作它们的工具,但我还没有尝试过。当然还有很多其他方法。
【讨论】:
有人知道 Eclipse 使用什么来完成这个任务吗?到目前为止,Eclipse 一直被用来构建这个应用程序,并且它总是制作一个包含所有内容的简洁包。用 Ant 完成同样的事情会很棒。将查看 one-jar 和 jarjar 看看我是否可以将其挂钩到 ant 进程中。 我不得不说,你的这个建议对我来说是救命稻草。非常感谢.. 效果很好! 我也想知道 Eclipse 是怎么做到的。 没关系,我想通了。答案是这样的:当 Eclipse 构建 jar 文件时,它会捆绑在一个名为 org.eclipse.jarinjar 或类似名称的自己的小粘合剂中。它是一个自定义类加载器,它知道如何从嵌入的 jar 文件中提取所需的类。我希望 one-jar 和 JarJar 做同样的事情。【参考方案4】:两个选项,要么引用类路径中的新 jar,要么解压缩封闭 jar 中的所有类并重新打包!据我所知,不推荐在罐子内包装罐子,并且您将永远拥有未找到类的异常!
【讨论】:
我只需要相信你。我的 java-fu 没那么强。但是我认为这个过程是微不足道的,因为 Eclipse 设法做到了。 ;)【参考方案5】:正如 Cheesle 所说,您可以解压缩和您的库罐子,并通过以下修改重新将它们全部重新罐子。
<jar destfile="$jar.file"
basedir="$build.dir"
manifest="$manifest.file">
<fileset dir="$classes.dir" includes="**/*.class" />
<zipgroupfileset dir="$lib.dir" includes="**/*.jar" />
</jar>
Jar 文件实际上只是嵌入了清单文件的 zip 文件。您可以将依赖项 Jar 提取并重新打包到应用程序的 Jar 文件中。
http://ant.apache.org/manual/Tasks/zip.html “Zip 任务还支持将多个 zip 文件合并到 zip 文件中。这可以通过任何嵌套文件集的 src 属性或使用特殊的嵌套文件集 zipgroupfileset 来实现。”
请注意与您的依赖库相关的许可证。从外部链接到库和在应用程序中包含库在法律上是完全不同的事情。
编辑 1:该死的我打字慢。 Grodriguez 打败了我。 :)
编辑 2:如果您决定不能将依赖项包含到应用程序中,则必须在启动时的命令行或通过清单文件在 Jar 的类路径中指定它们。 ANT 中有一个很好的命令可以为您处理清单文件中类路径的特殊格式。
<manifestclasspath property="manifest.classpath" jarfile="$jar.file">
<classpath location="$lib.dir" />
</manifestclasspath>
<manifest file="$manifest.file" >
<attribute name="built-by" value="$user.name" />
<attribute name="Main-Class" value="$main.class" />
<attribute name="Class-Path" value="$manifest.classpath" />
</manifest>
【讨论】:
【参考方案6】:这是运行可执行 jar 时的类路径问题,如下所示:
java -jar myfile.jar
解决问题的一种方法是在java命令行上设置类路径如下,添加缺少的log4j jar:
java -cp myfile.jar:log4j.jar:otherjar.jar com.abc.xyz.MyMainClass
当然最好的解决方案是将类路径添加到 jar 清单中,以便我们可以使用“-jar”java 选项:
<jar jarfile="myfile.jar">
..
..
<manifest>
<attribute name="Main-Class" value="com.abc.xyz.MyMainClass"/>
<attribute name="Class-Path" value="log4j.jar otherjar.jar"/>
</manifest>
</jar>
以下答案演示了如何使用manifestclasspath 自动查看类路径清单条目
Cannot find Main Class in File Compiled With Ant
【讨论】:
这显然不是 OP 想要的。从他的 build.xml 文件看来,他实际上想在他生成的 jar 文件中包含所有必需的 jar。 这是正确的格罗德里格兹。我的目标是让我们的用户只下载一个文件并运行它。将所有内容都包含在一个存档中很容易。【参考方案7】:我正在使用 NetBeans,并且还需要一个解决方案。在谷歌搜索并从克里斯托弗的回答开始之后,我设法构建了一个脚本,可以帮助您在 NetBeans 中轻松地做到这一点。我将说明放在这里以防其他人需要它们。
你要做的是下载一个jar。您可以使用此处的链接:http://one-jar.sourceforge.net/index.php?page=getting-started&file=ant 提取 jar 存档并查找包含 one-jar-ant-task-.jar、one-jar-ant-task.xml 和 one-jar-boot- 的 one-jar\dist 文件夹。罐。提取它们或将它们复制到我们将添加到下面脚本中的路径,作为属性 one-jar.dist.dir 的值。
只需将以下脚本复制到 build.xml 脚本末尾(来自您的 NetBeans 项目),就在 /project 标记之前,将 one-jar.dist.dir 的值替换为正确的路径并运行 one-jar目标。
对于那些不熟悉运行目标的人,本教程可能会有所帮助:http://www.oracle.com/technetwork/articles/javase/index-139904.html。它还向您展示了如何将源放入一个 jar 中,但它们是爆炸的,而不是压缩到 jar 中。
<property name="one-jar.dist.dir" value="path\to\one-jar-ant"/>
<import file="$one-jar.dist.dir/one-jar-ant-task.xml" optional="true" />
<target name="one-jar" depends="jar">
<property name="debuglevel" value="source,lines,vars"/>
<property name="target" value="1.6"/>
<property name="source" value="1.6"/>
<property name="src.dir" value="src"/>
<property name="bin.dir" value="bin"/>
<property name="build.dir" value="build"/>
<property name="dist.dir" value="dist"/>
<property name="external.lib.dir" value="$dist.dir/lib"/>
<property name="classes.dir" value="$build.dir/classes"/>
<property name="jar.target.dir" value="$build.dir/jars"/>
<property name="final.jar" value="$dist.dir/$ant.project.name.jar"/>
<property name="main.class" value="$main.class"/>
<path id="project.classpath">
<fileset dir="$external.lib.dir">
<include name="*.jar"/>
</fileset>
</path>
<mkdir dir="$bin.dir"/>
<!-- <mkdir dir="$build.dir"/> -->
<!-- <mkdir dir="$classes.dir"/> -->
<mkdir dir="$jar.target.dir"/>
<copy includeemptydirs="false" todir="$classes.dir">
<fileset dir="$src.dir">
<exclude name="**/*.launch"/>
<exclude name="**/*.java"/>
</fileset>
</copy>
<!-- <echo message="$ant.project.name: $ant.file"/> -->
<javac debug="true" debuglevel="$debuglevel" destdir="$classes.dir" source="$source" target="$target">
<src path="$src.dir"/>
<classpath refid="project.classpath"/>
</javac>
<delete file="$final.jar" />
<one-jar destfile="$final.jar" onejarmainclass="$main.class">
<main>
<fileset dir="$classes.dir"/>
</main>
<lib>
<fileset dir="$external.lib.dir" />
</lib>
</one-jar>
<delete dir="$jar.target.dir"/>
<delete dir="$bin.dir"/>
<delete dir="$external.lib.dir"/>
</target>
祝你好运,如果对你有帮助,别忘了投票。
【讨论】:
【参考方案8】:您可以使用一些已经内置到 ant jar 任务中的功能。
如果您转到The documentation for the ant jar task 并向下滚动到“合并档案”部分,则会有一个 sn-p 用于将所有 jar 中的所有 *.class 文件包含在“lib/main”目录中:
<jar destfile="build/main/checksites.jar">
<fileset dir="build/main/classes"/>
<restrict>
<name name="**/*.class"/>
<archives>
<zips>
<fileset dir="lib/main" includes="**/*.jar"/>
</zips>
</archives>
</restrict>
<manifest>
<attribute name="Main-Class" value="com.acme.checksites.Main"/>
</manifest>
</jar>
这创建一个带有主类“com.acme.checksites.Main”的可执行 jar 文件,并将所有 jar 中的所有类嵌入 lib/main 中。
如果发生命名空间冲突、重复和类似的事情,它不会做任何聪明的事情。此外,它将包含所有类文件,包括您不使用的类文件,因此合并后的 jar 文件将是完整大小。
如果你需要更高级的东西,看看one-jar和jar jar links
【讨论】:
以上是关于使用 Ant 在新的 jar 文件构建中包含外部 jar 文件的主要内容,如果未能解决你的问题,请参考以下文章
无法使用 Ant 从外部 jar 构建多个 Android dex 文件
Android 项目使用 Ant 构建良好,但出现运行时错误