【问题标题】:Including external jar-files in a new jar-file build with Ant使用 Ant 在新的 jar 文件构建中包含外部 jar 文件
【发布时间】: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,但这也是一个挑战。
  • 我确实设法找到了一个好的方法来完成我想要的。详情见下文。

标签: java eclipse ant jar


【解决方案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>

更灵活、更强大的解决方案是JarJarOne-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-jarjar jar links

      【讨论】:

        猜你喜欢
        • 2013-09-23
        • 2021-12-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-18
        • 1970-01-01
        • 2012-03-10
        • 2013-11-07
        相关资源
        最近更新 更多