Включение внешних jar-файлов в новую сборку jar-файла с помощью Ant
Я только что "унаследовал" 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 ответов
С полезными советами от людей, которые ответили здесь, я начал копаться в 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>
Я надеюсь, что кто-то еще может извлечь выгоду из этого.
Из вашего файла сборки 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. Посмотрите на них, если вышеперечисленное не удовлетворяет вашим требованиям.
Cheesle прав. Загрузчик классов не может найти встроенные банки. Если вы поместите достаточно команд отладки в командную строку, вы увидите, что команда java не может добавить файлы jar в classpath
То, что вы хотите сделать, иногда называют "uberjar". Я нашел один сосуд в качестве инструмента, чтобы помочь сделать их, но я не пробовал. Конечно, есть много других подходов.
Вы можете использовать немного функциональности, которая уже встроена в задачу 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>
Это создает исполняемый файл jar с основным классом com.acme.checksites.Main и встраивает все классы из всех jar в lib / main.
Он не сделает ничего умного в случае конфликтов пространства имен, дубликатов и тому подобного. Кроме того, он будет включать в себя все файлы классов, в том числе те, которые вы не используете, поэтому объединенный файл JAR будет иметь полный размер.
Если вам нужны более продвинутые вещи, подобные этим, взгляните на ссылки типа one-jar и jar jar
Как сказал Чизл, вы можете распаковать и вашу библиотеку 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>
Я использую NetBeans и мне тоже нужно решение для этого. После того, как гуглил и начал с ответа Кристофера, мне удалось создать скрипт, который поможет вам легко сделать это в NetBeans. Я помещаю инструкции здесь на случай, если они понадобятся кому-то другому.
То, что вам нужно сделать, это скачать одну банку. Вы можете использовать ссылку здесь: http://one-jar.sourceforge.net/index.php?page=getting-started&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>
Желаем удачи и не забудьте проголосовать, если это помогло вам.
Это проблема classpath при запуске исполняемого файла jar следующим образом:
java -jar myfile.jar
Один из способов решения этой проблемы - установить classpath в командной строке java следующим образом, добавив отсутствующий jar log4j:
java -cp myfile.jar:log4j.jar:otherjar.jar com.abc.xyz.MyMainClass
Конечно, лучшее решение - добавить classpath в манифест 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
Два варианта: либо ссылаться на новые jar-файлы в вашем пути к классам, либо распаковать все классы во вложенных jar-файлах и заново собрать все файлы! Насколько я знаю, упаковка банок внутри банок не рекомендуется, и у вас навсегда будет исключение класса не найден!