google web font

вторник, 25 августа 2015 г.

Как сделать jar, который может сам себя выполнить


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

Типичное использование:
$ java -jar my-fancy-app.jar
Для того, чтобы это работало, нужно при сборке jar-файла указать исполняемый класс (MainClass), это всем известно, абитуриентов не принимают на первый курс, если они этого не знают.

Все программы, которые умеют делать что-то полезное, используют какие-то библиотеки, написанные другими людьми и заранее упакованные в другие -jar-файлы. Чтобы моё приложение могло их подцепить, мне нужно добавить их в classpath. Но когда я запускаю -jar, я не могу указать дополнительно другой classpath. Я не склонен винить кого-либо в этом ограничении, вопрос "почему" не настолько сильно меня беспокоит. Больше интересно, что делать.

Популярное решение: "uber-jar". Говорят, раньше у maven был плагин, который так назывался (сейчас для этого используется плагин shade). Он архивирует jar'ы зависимостей внутрь jar'а моего приложения и прописывает свой MainClass, который устанавливает свой classloader, а потом возвращает управление моей программе. Classloader загружает библиотеки из вложенных jar-файлов и не сильно отличается от того, который описан в одном из первых уроков по java на сайте оракла. Раньше, говорят, был плагин, который вообще jar'ы зависимостей распаковывал прямо в мой jar, ему даже classloader подменять не надо было.

Это решение может не работать в ситуации, когда одна из зависимостей имеет злобную лицензию, не позволяющую паковать её в свой jar. Поскольку указать дополнительный classpath при использовании ключа -jar нельзя, остаётся два варианта: поставить эту библиотеку в систему (типа $JAVA_HOME/lib/) либо не использовать ключ -jar. Как это не использовать?

Вместо этого можно добавить свой jar в classpath и запускать как
$ java -cp my-fancy-app.jar my.fancy.Application
То есть указав исполняемый класс. В моём случае, поскольку часть зависимостей упакована внутрь uber-jar'а, мне нужно использовать другой исполняемый класс, который волшебным образом сам знает, что дальше делать. Ну, и запускать, соответственно, всё это надо командой вида
$ java -cp that-proprietary-labrary.jar:my-fancy-app.jar org.springframework.boot.ICantRememberClassName
Я не представляю, как я могу заставить другого человека это всё набрать в консоли, поэтому мне нужен shell-скрипт для запуска. Система становится сложной, но, к счастью, в скрипте можно ограничиться всего одной строчкой, поэтому всё не так ужасно. Если не считать, что скрипт из одной строчки — это тоже вообще-то ужасно.

И, наконец, интересное. Этот скрипт можно вписать непосредственно в мой jar-файл. Я пробовал открыть его vim'ом и написать туда нужную команду, не работает: vim испортил бинарный файл. Поэтому пришлось делать так:

$ cat > my-fancy-app <<EOF <target/my-fancy-app-1.0-SNAPSHOT.jar
> #!/bin/sh
> exec java -cp "that-proprietary-labrary.jar:\$0" my.application.MainClass
> EOF
Что произошло: мы создали скрипт из одной строки, который запускает java, а в хвосте этого скрипта идёт наш jar, который полностью работоспособен и его все ещё можно запускать как
$ java -cp my-fancy-app my.fancy.Application
Уже без расширения .jar, потому что мой новый файл не имеет расширения.

Однако, если сделать получившийся файл исполняемым, то его можно просто запустить в консоли, прямо вот так: $ ./my-fancy-app, более того, его можно положить в /usr/bin и запускать как системное приложение, можно даже сделать symlink в /etc/init.d/ и запускать при старте системы, только тогда скрипт должен будет состоять из более, чем одной строки. Можно прописать свои параметры java-машины, пути к ресурсам и внешним зависимостям, вывести сообщение, требующее от пользователя скачать that-proprietary-labrary.jar,ну в общем это в полном смысле shell-скрипт, который одновременно является в полном смысле jar-файлом и во всех смыслах является исполняемым.

Вот, такая java. Ввели странные ограничения и дали странные способы их обойти. Увлекательно. Spring-boot, кстати, позволяет собирать такой jar-файл "из коробки", там сразу настоящий init-скрипт внутри. Я ограничился однострочником, чтобы просто продемонстрировать концепцию.

Комментариев нет:

Отправить комментарий