38 Stimmen

Einbindung externer jar-Dateien in einen neuen jar-Dateibau mit Ant

Ich habe gerade ein Java-Projekt "geerbt", und da ich nicht aus dem Java-Umfeld komme, bin ich manchmal ein wenig verloren. Eclipse wird zum Debuggen und Ausführen der Anwendung während der Entwicklung verwendet. Mit Hilfe von Eclipse ist es mir gelungen, eine .jar-Datei zu erstellen, die alle erforderlichen externen Jars wie Log4J, xmlrpc-server usw. "enthält". Diese große .jar-Datei kann dann erfolgreich mit ausgeführt werden:

java -jar myjar.jar

Mein nächster Schritt besteht darin, Builds mit Ant (Version 1.7.1) zu automatisieren, damit ich Eclipse nicht für Builds und Deployment einsetzen muss. Dies hat sich aufgrund meiner mangelnden Java-Kenntnisse als Herausforderung erwiesen. Die Wurzel des Projekts sieht wie folgt aus:

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

Meine build.xml enthält das Folgende:

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

Ich laufe dann: ant clean build-jar

und eine Datei mit dem Namen seraph.jar wird im java/bin/jar-Verzeichnis abgelegt. Ich versuche dann, diese Jar-Datei mit dem folgenden Befehl auszuführen: java -jar bin/jar/seraph.jar

Das Ergebnis ist diese Ausgabe auf der Konsole:

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.

Ich vermute, dass ich etwas erstaunlich dummes in der build.xml-Datei gemacht habe und habe den größten Teil von zwei Tagen damit verbracht, Variationen der Konfiguration auszuprobieren, ohne Erfolg. Jede Hilfe auf immer diese Arbeit ist sehr geschätzt.

Oh, und es tut mir leid, wenn ich einige wichtige Informationen ausgelassen habe. Dies ist mein erster Beitrag hier bei SO.

0 Stimmen

An Ihrer Stelle würde ich Maven 2 oder Maven 3 anstelle von Ant verwenden, aber auch das wäre eine Herausforderung.

0 Stimmen

Ich habe es geschafft, einen guten Weg zu finden, um das zu erreichen, was ich wollte. Siehe unten für die Details.

50voto

Grodriguez Punkte 21042

Ihrem Ant-Buildfile entnehme ich, dass Sie ein einzelnes JAR-Archiv erstellen wollen, das nicht nur Ihre Anwendungsklassen, sondern auch den Inhalt anderer JARs enthält, die für Ihre Anwendung erforderlich sind.

Doch Ihr build-jar Datei nur die erforderlichen JARs in Ihr eigenes JAR einfügt; dies funktioniert nicht wie erklärt aquí (siehe Anmerkung).

Versuchen Sie, dies zu ändern:

<jar destfile="${jar.file}" 
    basedir="${build.dir}" 
    manifest="${manifest.file}">
    <fileset dir="${classes.dir}" includes="**/*.class" />
    <fileset dir="${lib.dir}" includes="**/*.jar" />
</jar>

dazu:

<jar destfile="${jar.file}" 
    basedir="${build.dir}" 
    manifest="${manifest.file}">
    <fileset dir="${classes.dir}" includes="**/*.class" />
    <zipgroupfileset dir="${lib.dir}" includes="**/*.jar" />
</jar>

Flexiblere und leistungsfähigere Lösungen sind die JarJar o Ein-Glas Projekte. Schauen Sie sich diese Projekte an, wenn die oben genannten Ihre Anforderungen nicht erfüllen.

0 Stimmen

Dadurch wird das Jar extrahiert und die Klassendateien in das endgültige Jar aufgenommen. Es zeigt dasselbe Verhalten in meiner Build-Datei. Ich habe meine Frage gepostet これ .

15voto

Christopher Punkte 1104

Dank der hilfreichen Ratschläge von Leuten, die hier geantwortet haben, habe ich angefangen, mich mit One-Jar zu beschäftigen. Nach einigen Sackgassen (und einigen Ergebnissen, die genau wie meine vorherigen Ergebnisse waren) habe ich es geschafft, es zum Laufen zu bringen. Als Referenz für andere führe ich hier die build.xml auf, die bei mir funktioniert hat.

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

Ich hoffe, jemand anderes kann davon profitieren.

0 Stimmen

Hallo, alte Antwort, ich weiß, aber wie sollte das Manifest für Ihr Beispiel aussehen? Edit: Habs, one-jar baut es selbst.

2voto

Julian Simpson Punkte 587

Cheesle ist richtig. Es gibt keine Möglichkeit für den Classloader, die eingebetteten Jars zu finden. Wenn Sie genügend Debug-Befehle in die Befehlszeile eingeben, sollten Sie sehen können, dass der Befehl "java" die Jars nicht zum Klassenpfad hinzufügen kann

Das, was Sie machen wollen, wird manchmal als "Uberjar" bezeichnet. Ich fand Ein-Glas als Hilfsmittel für die Herstellung, aber ich habe es nicht ausprobiert. Sicher gibt es viele andere Ansätze.

0 Stimmen

Weiß jemand genau, wie Eclipse dies bewerkstelligt? Bis jetzt wurde Eclipse verwendet, um diese Anwendung zu erstellen, und es hat immer ein ordentliches Paket mit allem, was enthalten ist, gemacht. Es wäre wunderbar, das Gleiche mit Ant zu erreichen. Ich werde einen Blick auf one-jar und jarjar werfen, um zu sehen, ob ich das in den Ant-Prozess einbinden kann.

0 Stimmen

Ich muss sagen, dass dieser Vorschlag von Ihnen für mich ein Lebensretter war. Vielen Dank es funktioniert großartig!

0 Stimmen

Auch ich würde gerne wissen, wie Eclipse das macht.

0voto

Cheesle Punkte 126

Es gibt zwei Möglichkeiten: Entweder referenzieren Sie die neuen Jars in Ihrem Klassenpfad oder Sie entpacken alle Klassen in den umschließenden Jars und jaren das Ganze neu! Soweit ich weiß, ist das Packen von Jars innerhalb von Jars nicht empfehlenswert und Sie werden für immer die Ausnahme "Klasse nicht gefunden" haben!

0 Stimmen

Das muss ich Ihnen einfach glauben. Mein Javafu ist nicht so stark. Allerdings hatte ich gedacht, dass der Prozess trivial ist, da Eclipse es schafft, es richtig zu machen ;)

0voto

Tansir1 Punkte 1749

Wie Cheesle schon sagte, können Sie Ihre Bibliotheks-Jars entpacken und mit der folgenden Änderung erneut entpacken.

    <jar destfile="${jar.file}"  
        basedir="${build.dir}"  
        manifest="${manifest.file}"> 
        <fileset dir="${classes.dir}" includes="**/*.class" /> 
        <zipgroupfileset dir="${lib.dir}" includes="**/*.jar" /> 
    </jar> 

Jar-Dateien sind eigentlich nur Zip-Dateien mit einer eingebetteten Manifestdatei. Sie können die Jar-Dateien der Abhängigkeiten extrahieren und in die Jar-Datei Ihrer Anwendung umpacken.

http://ant.apache.org/manual/Tasks/zip.html "Die Zip-Aufgabe unterstützt auch das Zusammenführen mehrerer Zip-Dateien in eine Zip-Datei. Dies ist entweder über das src-Attribut eines verschachtelten Dateisets oder durch Verwendung des speziellen verschachtelten Dateisets zipgroupfileset möglich."

Achten Sie auf die Lizenzen, die mit Ihren Abhängigkeits-Bibliotheken verbunden sind. Extern auf eine Bibliothek zu verlinken und die Bibliothek in Ihre Anwendung einzubinden, sind rechtlich gesehen sehr unterschiedliche Dinge.

EDIT 1: Verdammt, ich tippe zu langsam. Grodriguez ist mir zuvorgekommen :)

EDIT 2: Wenn Sie beschließen, dass Sie Ihre Abhängigkeiten nicht in Ihre Anwendung einbinden können, dann müssen Sie sie im Klassenpfad Ihres Jars angeben, entweder auf der Befehlszeile beim Start oder über die Manifest-Datei. Es gibt einen netten Befehl in ANT, um die spezielle Formatierung des Klassenpfads in einer Manifest-Datei für Sie zu handhaben.

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X