web-dev-qa-db-fra.com

Inclusion de fichiers JAR externes dans une nouvelle version de fichier JAR avec Ant

Je viens de "hériter" d'un projet Java et ne venant pas d'un contexte Java, je suis parfois un peu perdu. Eclipse est utilisé pour déboguer et exécuter l'application pendant le développement. Grâce à Eclipse, j'ai réussi à créer un fichier .jar qui "inclut" tous les fichiers jar externes requis, tels que Log4J, xmlrpc-server, etc.

Java -jar myjar.jar

Ma prochaine étape consiste à automatiser les générations à l’aide d’Ant (version 1.7.1) afin de ne pas avoir à faire appel à Eclipse pour effectuer des constructions et un déploiement. Cela s'est avéré être un défi en raison de mon manque de connaissances en Java. La racine du projet ressemble à ceci:

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

Mon build.xml contient les éléments suivants:

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

Je lance alors: pot de construction de fourmi propre

et un fichier nommé seraph.jar est placé dans le répertoire Java/bin/jar. J'essaie ensuite d'exécuter ce fichier en utilisant la commande suivante: Java -jar bin/jar/seraph.jar

Le résultat est cette sortie sur la console:

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.

Je suppose que j'ai fait quelque chose d'extraordinairement stupide dans le fichier build.xml et que j'ai passé la majeure partie de deux jours à essayer des variantes de la configuration, en vain. Toute aide pour que cela fonctionne est grandement appréciée.

Oh, et je suis désolé si j'ai laissé des informations cruciales. C'est la première fois que je poste ici à SO.

38
Christopher

Avec les conseils utiles des personnes qui ont répondu ici, j'ai commencé à creuser dans One-Jar. Après des impasses (et des résultats identiques à ceux de mes précédents), j'ai réussi à le faire fonctionner. Pour les autres peuples, je donne la liste du fichier build.xml qui a fonctionné pour moi.

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

J'espère que quelqu'un d'autre peut en bénéficier.

15
Christopher

Dans votre fichier de génération ant, je suppose que vous souhaitez créer une archive JAR unique qui contiendra non seulement vos classes d'application, mais également le contenu des autres JAR requis par votre application.

Cependant, votre fichier build-jar ne fait que placer les fichiers JAR requis dans votre propre fichier JAR; cela ne fonctionnera pas comme expliqué ici (voir note).

Essayez de modifier ceci:

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

pour ça:

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

Les projets JarJar ou One-Jar sont des solutions plus souples et plus puissantes. Jetez un coup d'œil à ceux-ci si ce qui précède ne répond pas à vos exigences.

49
Grodriguez

Cheesle a raison. Le chargeur de classes n'a aucun moyen de trouver les fichiers JAR intégrés. Si vous mettez suffisamment de commandes de débogage sur la ligne de commande, vous devriez pouvoir voir la commande 'Java' ne pas pouvoir ajouter les fichiers jar à un chemin de classe.

Ce que vous voulez faire est parfois appelé "uberjar". J'ai trouvé one-jar comme un outil pour les fabriquer, mais je ne l'ai pas essayé. Bien sûr, il y a beaucoup d'autres approches.

2
Julian Simpson

Comme Cheesle l’a dit, vous pouvez décompresser et réutiliser les bocaux de votre bibliothèque avec la modification suivante.

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

Les fichiers Jar ne sont en réalité que des fichiers Zip avec un fichier manifeste incorporé. Vous pouvez extraire et reconditionner les fichiers JAR de dépendance dans le fichier JAR de votre application. 

http://ant.Apache.org/manual/Tasks/Zip.html "La tâche Zip prend également en charge la fusion de plusieurs fichiers Zip dans le fichier Zip. Cela est possible grâce à l'attribut src de tout ensemble de fichiers imbriqué ou en utilisant l'ensemble zipgroupfileset spécial d'ensembles de fichiers imbriqués. "

Faites attention aux licences impliquées avec vos bibliothèques de dépendance. La liaison externe à une bibliothèque et l'inclusion de la bibliothèque dans votre application sont des choses très différentes légalement.

EDIT 1: Darn ma lente frappe. Grodriguez m'a battu. :)

EDIT 2: Si vous décidez que vous ne pouvez pas inclure vos dépendances dans votre application, vous devez les spécifier dans le chemin de classe de votre Jar, soit sur la ligne de commande au démarrage, soit via le fichier Manifest. ANT contient une commande Nice pour gérer la mise en forme spéciale du classpath dans un fichier Manifest pour vous.

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

Deux options: référencer les nouveaux fichiers jar dans votre chemin de classe ou décompresser toutes les classes dans les fichiers jar englobés et re-charger le tout Pour autant que je sache, l'emballage de pots dans des pots n'est pas recommandé et vous aurez toujours la classe exception non trouvée!

0
Cheesle

Il s'agit d'un problème de chemin de classe lors de l'exécution d'un fichier jar exécutable, comme suit:

Java -jar myfile.jar

Une façon de résoudre le problème consiste à définir le chemin d'accès aux classes sur la ligne de commande Java comme suit, en ajoutant le fichier jar manquant log4j:

Java -cp myfile.jar:log4j.jar:otherjar.jar com.abc.xyz.MyMainClass

Bien sûr, la meilleure solution consiste à ajouter le chemin d'accès aux classes dans le manifeste jar afin que nous puissions utiliser l'option Java "-jar":

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

La réponse suivante montre comment vous pouvez utiliser manifestclasspath pour automatiser la lecture de l'entrée de manifeste de chemin de classe.

Impossible de trouver la classe principale dans un fichier compilé avec Ant

0
Mark O'Connor

J'utilise NetBeans et j'avais besoin d'une solution pour cela également. Après avoir parcouru tous les horizons et commencé avec la réponse de Christopher, j'ai réussi à créer un script qui vous aide à le faire facilement dans NetBeans. Je mets les instructions ici au cas où quelqu'un d'autre en aurait besoin.

Ce que vous devez faire, c'est télécharger one-jar. Vous pouvez utiliser le lien à partir d'ici: http://one-jar.sourceforge.net/index.php?page=getting-started&file=ant Extrayez l'archive jar et cherchez one-jar\dist dossier contenant one-jar-ant-task -.jar, one-jar-ant-task.xml et one-jar-boot- .jar. Extrayez-les ou copiez-les dans un chemin que nous ajouterons au script ci-dessous, en tant que valeur de la propriété one-jar.dist.dir.

Copiez simplement le script suivant à la fin de votre script build.xml (à partir de votre projet NetBeans), juste avant la balise/project, remplacez la valeur de one-jar.dist.dir par le chemin correct et exécutez one-jar target.

Pour ceux d'entre vous qui ne connaissent pas les cibles en cours d'exécution, ce didacticiel peut vous aider: http://www.Oracle.com/technetwork/articles/javase/index-139904.html . Il vous montre également comment placer les sources dans un seul pot, mais elles sont éclatées et non compressées dans des pots.

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

Bonne chance et n'oubliez pas de voter si cela vous a aidé.

0
Alex Burdusel

Vous pouvez utiliser un peu des fonctionnalités déjà intégrées à la tâche ant jar. 

Si vous allez dans La documentation de la tâche ant jar et faites défiler jusqu'à la section "fusion d'archives", vous trouverez un extrait pour inclure tous les fichiers * .class de tous les fichiers jar de votre répertoire "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>

Cette crée un fichier jar exécutable avec une classe principale "com.acme.checksites.Main" et incorpore toutes les classes de tous les jars de lib/main.

Cela ne fera rien d'intelligent en cas de conflits d'espace de noms, de doublons et autres choses du même genre. En outre, il inclura tous les fichiers de classe, y compris ceux que vous n'utilisez pas, de sorte que le fichier jar combiné aura une taille normale.

Si vous avez besoin de choses plus avancées comme celle-là, jetez un oeil à one-jar et jar jar links

0
s3v1