在使用Ant的新jar文件构建中包含外部jar文件

时间:2010-09-22 14:08:25

标签: java eclipse ant jar

我刚刚“继承”了一个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上发帖。

8 个答案:

答案 0 :(得分:49)

从你的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项目。如果以上内容不符合您的要求,请查看这些内容。

答案 1 :(得分:15)

在这里回答的人们提供了有用的建议后,我开始深入了解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>

我希望其他人可以从中受益。

答案 2 :(得分:2)

Cheesle是对的。类加载器无法找到嵌入式jar。如果在命令行上放置了足够的调试命令,您应该能够看到'java'命令无法将jar添加到类路径

你想要做的有时被称为'uberjar'。我发现one-jar是帮助制作它们的工具,但我还没有尝试过。当然还有很多其他方法。

答案 3 :(得分:0)

两个选项,要么引用类路径中的新jar,要么解压缩封闭jar中的所有类并重新包装整个jar!据我所知,罐子里的包装罐子没有被推荐,你将永远没有找到这个类别的例外!

答案 4 :(得分:0)

正如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文件。您可以将依赖项Jars提取并重新打包到应用程序的Jar文件中。

http://ant.apache.org/manual/Tasks/zip.html “Zip任务还支持将多个zip文件合并到zip文件中。这可以通过任何嵌套文件集的src属性或使用特殊的嵌套文件集zipgroupfileset来实现。”

请注意您的依赖库中涉及的许可证。从外部链接到库并在应用程序中包含库是合法的。

编辑1:我慢慢打字。格罗德里格斯打败了我。 :)

编辑2:如果您决定不能将依赖项包含到应用程序中,则必须在启动时的命令行或通过Manifest文件在Jar的类路径中指定它们。在ANT中有一个很好的命令来处理Manifest文件中类路径的特殊格式。

<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>

答案 5 :(得分:0)

这是运行可执行jar时的类路径问题,如下所示:

java -jar myfile.jar

解决问题的一种方法是在java命令行上设置classpath,如下所示,添加缺少的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

答案 6 :(得分:0)

我正在使用NetBeans,并且还需要一个解决方案。谷歌搜索并从克里斯托弗的答案开始后,我设法构建了一个脚本,可以帮助您轻松地在NetBeans中执行此操作。我将这些说明放在这里以防其他人需要它们。

你要做的就是下载一个罐子。您可以使用此处的链接: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。它还向您展示了如何将源放入一个罐子中,但它们是爆炸的,而不是压缩成罐子。

<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>

祝你好运,如果对你有所帮助,不要忘记投票。

答案 7 :(得分:0)

您可以使用已经内置于ant jar任务的一些功能。

如果你转到The documentation for the ant jar task并向下滚动到&#34;合并档案&#34;那里有一个片段,用于包含你所有罐子里的所有* .class文件&#34; lib / main&#34;目录:

<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>

这个创建一个带有主类&#34; com.acme.checksites.Main&#34;的可执行jar文件,并嵌入lib / main中所有jar的所有类。 < / p>

在命名空间冲突,重复和类似事情的情况下,它不会做任何聪明的事情。此外,它将包括所有类文件,也包括您不使用的文件,因此组合的jar文件将是完整大小。

如果您需要更高级的内容,请查看one-jarjar jar links