Статьи

Установка приложения Java в качестве службы Windows

Это звучит как то, что вам никогда не понадобится, но иногда, когда вы распространяете программное обеспечение для конечного пользователя, вам может потребоваться установить Java-программу в качестве службы Windows. Мне пришлось это сделать, потому что я разработал инструмент для государственных служащих, который автоматически конвертирует и передает свои файлы Excel на портал opendata своей страны. Инструмент должен периодически запускаться, поэтому он является основным кандидатом на услугу (что сделает загрузку возможной, даже если государственный служащий вообще забудет об этой задаче, и, кроме того, повторная загрузка вручную — пустая трата времени).

Несмотря на многочисленные сообщения и ответы на вопросы stackoverflow по этой теме, мне все равно потребовалось много времени из-за незначительных предостережений и одного важного условия, которое, казалось, имели немногие — наличие JRE в комплекте, так что никто не должен загружать и устанавливать JRE (усложнит процесс установки без необходимости, а целевая аудитория не обязательно будет разбираться в технологиях).

Итак, в проекте maven с jar-упаковкой я сначала подумал об упаковке exe- файлаlaunch4j ), а затем о регистрации его в качестве службы. Проблема в том, что Java-программа использует запланированный исполнитель, поэтому она никогда не завершается, что делает невозможным запуск ее как процесса.

Поэтому мне пришлось «демонизировать» его, используя procrun для commons-daemon . Перед этим мне пришлось собрать все необходимые компоненты в одну целевую папку — толстый файл jar (включая все зависимости), JRE, двоичные файлы commons-daemon и файл конфигурации.

Вы можете увидеть полный файл Maven здесь . Соответствующие биты (где ${installer.dir} равен ${project.basedir}/target/installer} ):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
</plugin>
<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <executions>
        <execution>
            <id>assembly</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <finalName>opendata-ckan-pusher</finalName>
                <appendAssemblyId>false</appendAssemblyId>
            </configuration>
        </execution>
    </executions>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.7</version>
    <executions>
        <execution>
            <id>default-cli</id>
            <phase>package</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <target>
                    <copy todir="${installer.dir}/jre1.8.0_91">
                        <fileset dir="${project.basedir}/jre1.8.0_91" />
                    </copy>
                    <copy todir="${installer.dir}/commons-daemon">
                        <fileset dir="${project.basedir}/commons-daemon" />
                    </copy>
                    <copy file="${project.build.directory}/opendata-ckan-pusher.jar" todir="${installer.dir}" />
                    <copy file="${project.basedir}/install.bat" todir="${installer.dir}" />
                    <copy file="${project.basedir}/uninstall.bat" todir="${installer.dir}" />
                    <copy file="${project.basedir}/config/pusher.yml" todir="${installer.dir}" />
                    <copy file="${project.basedir}/LICENSE" todir="${installer.dir}" />
                </target>
            </configuration>
        </execution>
    </executions>
</plugin>

Вы увидите, что это файлы installer.bat и uninstaller.bat, которые используют commons-daemon для управления сервисом. Установщик создает сервис. Commons-daemon имеет три режима: exe (который позволяет обернуть произвольный исполняемый файл), Java (который похож на exe, но для java-приложений) и jvm (который запускает java-приложение в одном и том же процессе; я не знаю, как именно так)

Я мог бы использовать все три варианта (включая launch4j, созданный exe), но jvm позволяет вам назначить метод для управления запущенным приложением. Для этого предназначены параметры StartClass / StartMethod / StopClass / StopMethod. Вот весь файл installer.bat:

1
2
3
4
5
6
7
8
9
commons-daemon\prunsrv //IS//OpenDataPusher --DisplayName="OpenData Pusher" --Description="OpenData Pusher"^
     --Install="%cd%\commons-daemon\prunsrv.exe" --Jvm="%cd%\jre1.8.0_91\bin\client\jvm.dll" --StartMode=jvm --StopMode=jvm^
     --Startup=auto --StartClass=bg.government.opendatapusher.Pusher --StopClass=bg.government.opendatapusher.Pusher^
     --StartParams=start --StopParams=stop --StartMethod=windowsService --StopMethod=windowsService^
     --Classpath="%cd%\opendata-ckan-pusher.jar" --LogLevel=DEBUG^ --LogPath="%cd%\logs" --LogPrefix=procrun.log^
     --StdOutput="%cd%\logs\stdout.log" --StdError="%cd%\logs\stderr.log"
      
      
commons-daemon\prunsrv //ES//OpenDataPusher

Несколько уточнений:

  • Параметр Jvm указывает на jvm dll (если честно, я не уверен, сработает ли это, если на машине нет другой установки java; она должна)
  • StartClass / StartMethod / StopClass / StopMethod указывают на назначенный метод для управления запущенным приложением. В этом случае запуск будет просто вызывать метод main, а остановка отключит запланированного исполнителя, чтобы приложение могло выйти
  • Параметр classpath указывает на толстую банку
  • Использование% cd% рискованно для определения пути к текущему каталогу, но, поскольку конечные пользователи всегда будут запускать его из каталога, в котором он находится, в этом случае это безопасно.

windowsService выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
13
public static void windowsService(String args[]) throws Exception {
     String cmd = "start";
     if (args.length > 0) {
        cmd = args[0];
    }
 
    if ("start".equals(cmd)) {
        Pusher.main(new String[]{});
    } else {
        executor.shutdownNow();
        System.exit(0);
    }
}

Здесь важно отметить 32-битную / 64-битную проблему, которая может у вас возникнуть. Вот почему безопаснее связать 32-битную JRE и использовать 32-битный (по умолчанию) prunsrv.exe.

Затем у меня была папка «installer» с папками jre и commons-daemon, двумя файлами bat и одним толстым jar. Затем я мог бы упаковать его в самораспаковывающийся архив и распространять (конечно, с помощью руководства). Я также посмотрел в IzPack , но не смог найти, как связать JRE (возможно, вы можете).

Это довольно нишевый сценарий — обычно мы разрабатываем для развертывания на сервере Linux, но время от времени могут потребоваться локальные инструменты для большой организации, использующей Java. В моем случае долгожданная часть была запланированным исполнителем, но она также может запустить сервис причала, который обслуживает веб-интерфейс. Зачем это делать, вместо предоставления URL — в тех случаях, когда доступ к локальной машине имеет значение. Это может быть даже распределенная поисковая система ( например ) или другое p2p-программное обеспечение, которое вы хотите написать на Java.