Java
Groovy & Grails
Build automation
7 September 2016

Превращаем Java/JVM приложение в демона

Tutorial
Часто ли вы пишите shell скрипт и пакетный файл для запуска своего jvm приложения, а как часто копируете из другого проекта?



Можно воспользоваться appassembler-maven-plugin для генерации скриптов запуска нашей программы и создания из нее демона. Плагин делает всю рутинную работу по конфигурации java service wrapper, генерации скриптов и сборке приложения за нас.

Но мы упростим нашу жизнь и воспользуемся автоматизированным решением для создание скелета maven артефактов для сборки своих демонов. Плагином-генератором com.github.igor-suhorukov:daemon-archetype для maven, который доступен в центральном репозитарии и на github. И под капотом все равно appassembler-maven-plugin!

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

mvn archetype:generate -DarchetypeGroupId=com.github.igor-suhorukov -DarchetypeArtifactId=daemon-archetype -DarchetypeVersion=0.1

Важное замечание! Демон java service wrapper останавливается сигналом SIGINT, поэтому для корректного освобождения ресурсов надо зарегистрировать свой Runtime.getRuntime().addShutdownHook(...).

Конфигурация плагина daemon-archetype.


Параметры groupId, artifactId, version не заслуживают особого внимания, так как они требуются любым archetype плагином и это то, что будет в соответствующих тегах pom.xml.

Параметром entry-point-class нужно указать полностью квалифицированное имя класса, метод public static void main(String[]) которого демон будет вызывать при старте. В случае java приложения можно указать класс с методом main как из зависимости проекта main-artifact*, так и из директории src/main/java текущего проекта.

Параметры main-artifact-artifactId, main-artifact-groupId, main-artifact-version указывают на зависимость, которая содержит entry-point-class. В сборку пакуются также транзитивные зависимости для main-artifact*.

launcher-name определяет имя скрипта демона в директории bin.

Пример: демон git сервера gitblit.


Для пробы создадим демон запуска git сервера. Выполнив в консоли команду, либо указав те же параметры в интерактивном режиме:

mvn archetype:generate -DarchetypeGroupId=com.github.igor-suhorukov -DarchetypeArtifactId=daemon-archetype -DarchetypeVersion=0.1 -DgroupId=com.github.igor-suhorukov -DartifactId=gitblit-launcher -Dversion=1.0-SNAPSHOT -Dpackage=com.github.igor-suhorukov -Dentry-point-class=com.github.igorsuhorukov.groovy.GroovyMain -Dlauncher-name=launcher -Dmain-artifact-artifactId=groovy-grape-aether -Dmain-artifact-groupId=com.github.igor-suhorukov -Dmain-artifact-version=2.4.5.4 -DinteractiveMode=false



После отредактируем получившийся pom.xml, добавив в теги program и daemon следующий фрагмент конфигурации:

<commandLineArguments>
    <commandLineArgument>https://raw.githubusercontent.com/igor-suhorukov/git-configuration/master/gitblit.groovy </commandLineArgument>
</commandLineArguments>

Выполняем команду:

mvn package

После сборки в директории target будет архив с демоном: gitblit-launcher-1.0-SNAPSHOT-daemon.tgz и два архива в формате tgz и zip с обычными скриптами для запуска консольного приложения gitblit-launcher-1.0-SNAPSHOT-assembly.tgz, gitblit-launcher-1.0-SNAPSHOT-assembly.zip.

В нашем примере в сборку с демоном упакуется jar файл из com.github.igor-suhorukov:groovy-grape-aether:2.4.5.4. Демон запустит JVM с указанием main класса com.github.igorsuhorukov.groovy.GroovyMain и передаст ему параметром путь к Groovy скрипту raw.githubusercontent.com/igor-suhorukov/git-configuration/master/gitblit.groovy.

Groovy скрипт скачивает gitblit.war из репозитария проекта, распаковывает его в домашнюю директорию пользователя и заменяет в конфигурации gitblit путь к хранилищу репозитариев. После этого запускает jetty сервер и gitblit внутри него.

gitblit.groovy
import com.github.igorsuhorukov.smreed.dropship.MavenClassLoader
@Grab(group='org.codehaus.plexus', module='plexus-archiver', version='2.10.2')
import org.codehaus.plexus.archiver.zip.ZipUnArchiver
@Grab(group='org.codehaus.plexus', module='plexus-container-default', version='1.6')
import org.codehaus.plexus.logging.console.ConsoleLogger
@Grab(group = 'org.eclipse.jetty', module = 'jetty-runner', version = '9.3.7.RC1' )
import org.eclipse.jetty.runner.Runner

def gitblit = new File(MavenClassLoader.using('http://gitblit.github.io/gitblit-maven').resolveArtifact('com.gitblit:gitblit:war:1.7.1').getFile())

File gitblitDirectory = new File(System.getProperty('user.home'), gitblit.getName().replace('.war',''))

if(!gitblitDirectory.exists()){
    gitblitDirectory.mkdir()
    ZipUnArchiver unArchiver = new ZipUnArchiver()
    unArchiver.setSourceFile(gitblit)
    unArchiver.enableLogging(new ConsoleLogger(ConsoleLogger.LEVEL_DEBUG,"Logger"))
    unArchiver.setDestDirectory(gitblitDirectory)
    unArchiver.extract()

    def dataPath = new File(System.getProperty('user.home'), '.gitblit_data')
    if(!dataPath.exists()){ dataPath.mkdir() }
    def webXml = new File(gitblitDirectory.getAbsoluteFile(), 'WEB-INF/web.xml')
    webXmlText = webXml.text
    webXml.withWriter { w -> w << webXmlText.replace('${contextFolder}/WEB-INF/data', dataPath.getAbsolutePath()) }
}

Runner.main([gitblitDirectory] as String[])

Можете посмотреть скринкаст с примером создания и работы демона.



Создать демон и скрипты для сборки приложения можно с помощью всего лишь одной команды.

+14
11.2k 60
Comments 15