понедельник, 5 апреля 2010 г.

Сборка исполняемых Jar файлов с включенными библиотеками при помощи Ant 1.8 или/or How to build a fat jar!

Вкратце о решаемой проблеме: Есть исходный код на языке Java, который требует дополнительных библиотек для выполнения. Требуемый результат: Один исполняемый jar архив, содержащий внутри все зависимости. В среде Eclipse есть такая функциональность ("File->Export->Runnable Jar File"), но задача требует этот процесс автоматизировать. Это мое первое знакомство с инструментом Ant, поэтому пишу все очень подробно, для того, чтобы человек с таким же уровнем мог разобраться в этом быстрее, чем это сделал я.

Нам понадобится
Также можете скачать все необходимые файлы примера, который мы тут рассматриваем (ссылочка). Сюда также включен "One-jar library" - так что его отдельно можно не скачивать.

Итак, приступим. Начнем с структуры проекта:
.
|-- build --- Сюда будем компилить классы и здесь же поместим полученную программу
|-- buildFatJar.xml --- ant script, описывающий весь необходимый нам процесс
|-- lib --- директория, содержащая библиотеки, используемые нашей программой
| |-- slf4j-api-1.5.11.jar
| `-- slf4j-simple-1.5.11.jar
|-- one-jar-ant-task-0.96.jar --- эта библиотека предназначена для Ant, дает возможность включать архивы библиотек в архив собственной исполняемой программы
`-- src --- папка с исходными кодами
`-- by
`-- sokol
`-- labs
`-- ant
`-- LibDependentUtil.java --- Класс, содержащий "main" method и использующий дополнительные библиотеки (import org.slf4j.*; )
Файлы исходного кода и библиотеки можно скачать одним архивом, перейдя по всё той же ссылке . И самое интересное, Ant script:

<project name="BuildFatJar" basedir="." default="main">
<!-- Импорт дополнительных задач к Ant для создания -->
<!-- исполняемых jar файлов с включенными зависимостями -->
<import>
<javaresource name="one-jar-ant-task.xml">
<classpath location="one-jar-ant-task-0.96.jar" />
</javaresource>
</import>
<!-- Директория с исходными кодами программы -->
<property name="src.dir" value="src" />
<!-- Путь, куда будет скомпилировано приложение -->
<property name="build.dir" value="build" />
<!-- Сюда будет помещен полученный jar -->
<property name="jar.dir" value="${build.dir}" />
<!-- Здесь находятся необходимые библиотеки -->
<property name="lib.dir" value="lib"/>
<!-- В эту папку будут помещены скомпилированные классы -->
<property name="classes.dir" value="${build.dir}/classes" />

<!-- Имя создаваемого исполняемого архива -->
<property name="jarname" value="FatJarExample" />

<target name="clean">
<delete dir="${build.dir}" />
</target>

<target name="compile">
<mkdir dir="${classes.dir}" />
<!-- Путь к необходимым библиотекам -->
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar, **/*.zip" />
</path>
<javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath" />
</target>

<!-- Создаем исполняемый jar с включенными зависимостями -->
<target name="jar" depends="compile">
<property name="main-class" value="by.sokol.labs.ant.LibDependentUtil" />
<property name="manifest.file" value="MANIFEST.MF" />
<!-- Этот файл будет использоваться для того, чтобы сделать результат исполняемым -->
<manifest file="${manifest.file}" id="manifest">
<!-- Этот аттрибут не следует изменять -->
<attribute name="Main-Class" value="com.simontuffs.onejar.Boot" />
<!-- Здесь указываем путь к точке запуска программы (класс, содержащий "main" method) -->
<!-- ${main-class} ссылается на значение определенное в начале данного скрипта -->
<attribute name="One-Jar-Main-Class" value="${main-class}" />
</manifest>
<mkdir dir="${jar.dir}" />
<!-- Эта дополнительная задача загружается из one-jar-ant-task-0.96.jar -->
<!-- Для ее загрузки используем импорт в начале данного скрипта -->
<one-jar destfile="${jar.dir}/${jarname}.jar" manifest="${manifest.file}">
<main>
<!-- Скомпилированные классы вашего приложения -->
<fileset dir="${classes.dir}" />
</main>
<lib>
<!-- Путь к необходимым библиотекам -->
<fileset dir="${lib.dir}" />
</lib>
</one-jar>
<delete file="${manifest.file}" />
</target>

<target name="delete_classes">
<delete deleteonexit="true" dir="${classes.dir}" />
</target>

<target name="build_app" depends="compile,jar,delete_classes" />

<target name="main" depends="clean,build_app" />

</project>


Финал! Собираем, для чего открываем терминал, переходим в директорию проекта и пишем: "ant -f buildFatJar.xml", читаем сообщение "BUILD SUCCESSFUL" и запускаем полученное приложение "java -jar build/FatJarExample.jar". Thats all!

Немного комментариев:
1) Почему нельзя просто запихнуть необходимые библиотеки в целевой архив?
Стандартный загрузчик классов не производит поиск в архивах, вложенных в другие архивы. Для этого и нужна библиотека "one-jar-ant-task-0.96.jar" - она предоставляет специальные загрузчик классов, который справляется с этой задачей. Одним из вариантов альтернативного решения будет распаковать необходимые библиотеки в папку с классами вашего приложения, после чего это все собирается как единый проект. Не пробовал, но уверен, что должно получиться.
2) Почему обязательно Ant 1.8?
Необязательно, Но в нем есть очень удобный способ загрузки новых задач
<import>
<javaresource name="one-jar-ant-task.xml">
<classpath location="one-jar-ant-task-0.96.jar" />
</javaresource>
</import>



вторник, 30 марта 2010 г.

Вот такой вопрос встретил в одном из тестов по джаве.


public class Labs {

public static void main(String[] args) {
try {
new Labs().throwExcept();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
private void throwExcept() throws Exception {
try {
throw new Exception("A");
} finally {
throw new Exception("B");
}
}
}