Можно воспользоваться 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 внутри него.
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[])
Можете посмотреть скринкаст с примером создания и работы демона.
Создать демон и скрипты для сборки приложения можно с помощью всего лишь одной команды.
Комментарии (15)
kloppspb
07.09.2016 15:58«Мы» делаем из java-приложения демона только тогда, когда этого невозможно избежать. Да и то стараемся обходить такой случай.
Контрольные вопросы ( само собой, они возникают как послеследствие jsvc и daemon(ize) ):
1) что с пид-файлом?
2) решили ли вы проблему HUP (напоминаю, что по-апачевски это start/stop, а по-сановски это «Signal is internal proprietary API and may be removed in a future release.»)
igor_suhorukov
07.09.2016 16:131) В директории logs создается файл launcher.pid (или как назвали свойство «launcher-name») создается при старте;
2) «wrapper.signal.mode.hup» поведение конфигурируется.kloppspb
07.09.2016 16:31То есть поймать сигнал и что-то сделать без гашения процесса всё равно не получится?
igor_suhorukov
07.09.2016 16:37Как вариант проприетарные sun.misc.Signal и sun.misc.SignalHandler или addShutdownHook, как указывал в публикации(и жди себе, пока не прийдет SIGKILL).
kloppspb
07.09.2016 16:42Так в том-то и дело :) Сановские ругательства javac на использование «sun.misc.Signal*» продолжаются уже много лет, но никак в реальную опасность не превращаются. Их и используем.
relgames
07.09.2016 16:27+2Мы используем spring-boot и systemd для этих целей. systemd вообще оказалось очень просто — никаких сложных скриптов, конфиг-файл для сервиса занимает 5-10 строк.
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started-first-application-executable-jar
http://docs.spring.io/spring-boot/docs/current/reference/html/deployment-install.html#deployment-systemd-service
chabapok
а зачем?
Оно вполне нормально запускается напрмер из upstart, и шатдаунится штатно.
igor_suhorukov
У java service wrapper есть функциональность watchdog, которая перезапускает JVM в случае зависания. stdout и stderr пишутся в ротируемые логи. Это первое что приходит на ум. А если ОС windows или solaris?
symbix
Upstart и systemd делают все то же штатно безо всяких врапперов. Лишний враппер им только мешает.
> если ОС windows
тогда надо запускать сервисом
> solaris
Я в нем совсем не разбираюсь, но разве в SMF нет штатной функциональности для new style daemons?
igor_suhorukov
Тот же враппер есть и под Windows, по-умолчанию конфигурируется в pom.xml "windows-x86-32".
Плюс нет необходимости в самой программе следить в разных платформах, что запущен только один ее процесс, это есть «из коробки».
Я вас не переубеждаю, каждый использует то что ему удобно. Лично мне так проще, так же как и разработчикам Nexus, Sonar и т.п.
igor_suhorukov
В солярисе я не специалист, но сколько раз сталкивался на работах с устаревшими серверами на solaris, каждый раз без проблем использовал java service wrapper.
Когда пытался решить штатными средствами запуск одного процесса в приложении, в том же jruby на solaris не работал file lock.