Включение внешних jar-файлов в новую сборку jar-файла с помощью Ant - PullRequest
38 голосов
/ 22 сентября 2010

Я только что «унаследовал» Java-проект и не пришел из Java-фона. Иногда я немного растерялся.Eclipse используется для отладки и запуска приложения во время разработки.Мне удалось через Eclipse создать .jar-файл, который «включает» все необходимые внешние jar-файлы, такие как Log4J, xmlrpc-сервер и т. Д. Этот большой .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 ]

49 голосов
/ 22 сентября 2010

Из вашего файла сборки ant я предполагаю, что вам нужно создать один JAR-архив, который будет содержать не только классы вашего приложения, но и содержимое других JAR-файлов, требуемых вашим приложением.

Однако ваш build-jar файл просто помещает необходимые файлы JAR в ваш собственный файл JAR; это не будет работать, как объяснено здесь (см. примечание).

Попробуйте изменить это:

<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 . Изучите их, если вышеперечисленное не удовлетворяет вашим требованиям.

15 голосов
/ 23 сентября 2010

С полезными советами от людей, которые ответили здесь, я начал копаться в 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 голосов
/ 22 сентября 2010

Чизль прав. Загрузчик классов не может найти встроенные банки. Если вы поместите достаточно команд отладки в командную строку, вы увидите, что команда java не может добавить файлы jar в classpath

То, что вы хотите сделать, иногда называют «uberjar». Я нашел one-jar как инструмент, чтобы помочь сделать их, но я не пробовал. Конечно, есть много других подходов.

0 голосов
/ 28 июня 2014

Вы можете использовать немного функциональности, которая уже встроена в задачу ant jar.

Если перейти к Документация для задачи ant jar и прокрутить вниз до раздела «слияние архивов», то есть фрагмент для включения всех файлов * .class из всех jar-файлов вваш каталог "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>

This Создает исполняемый файл jar с основным классом "com.acme.checksites.Main" и встраивает все классы из всех jar-файлов вlib / main.

Он не сделает ничего умного в случае конфликтов пространства имен, дубликатов и тому подобного.Кроме того, он будет включать в себя все файлы классов, в том числе те, которые вы не используете, поэтому объединенный файл JAR будет иметь полный размер.

Если вам нужны более сложные вещи, подобные этим, посмотрите на ссылки на одну банку и ссылки на банку

0 голосов
/ 04 июля 2013

Я использую NetBeans, и для этого тоже нужно решение. После того, как гуглил и начал с ответа Кристофера, мне удалось создать скрипт, который поможет вам легко сделать это в NetBeans. Я помещаю инструкции здесь на случай, если они понадобятся кому-то другому.

То, что вам нужно сделать, это загрузить одну банку. Вы можете использовать ссылку здесь: http://one -jar.sourceforge.net / index.php? Page = начало работы & file = ant Извлеките архив jar и найдите папку one-jar \ dist, которая содержит файлы one-jar-ant-task- .jar, one-jar-ant-task.xml и one-jar-boot- .jar , Извлеките их или скопируйте их по пути, который мы добавим в скрипт ниже, в качестве значения свойства 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>

Желаем удачи, и не забудьте проголосовать, если это помогло вам.

0 голосов
/ 22 сентября 2010

Это проблема classpath при запуске исполняемого файла jar следующим образом:

java -jar myfile.jar

Один из способов решения этой проблемы - установить classpath в командной строке 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 для автоматизации просмотра записи манифеста classpath

Не удается найти главный класс в файле, скомпилированном с Ant

0 голосов
/ 22 сентября 2010

Как сказал Чизл, вы можете распаковать и вашу библиотеку Jars и повторно собрать их все со следующей модификацией.

    <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: Черт возьми, моя медленная печать.Гродригес избил меня до этого.:)

РЕДАКТИРОВАТЬ 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>
0 голосов
/ 22 сентября 2010

Два варианта: либо ссылаться на новые jar-файлы в вашем пути к классам, либо распаковать все классы во вложенных jar-файлах и заново собрать все файлы!Насколько я знаю, упаковка банок внутри банок не рекомендуется, и у вас навсегда будет исключение класса не найден!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...