【问题标题】:Spring boot JAR as windows serviceSpring Boot JAR 作为 Windows 服务
【发布时间】:2014-07-30 05:12:04
【问题描述】:

我正在尝试用 procrun 包装弹簧靴“uber JAR”。

按预期运行以下工作:

java -jar my.jar

我需要我的 spring boot jar 在 Windows 启动时自动启动。最好的解决方案是将 jar 作为服务运行(与独立的 tomcat 相同)。

当我尝试运行它时,我收到“Commons Daemon procrun failed with exit value: 3”

查看 spring-boot 源,它看起来好像使用了自定义类加载器:

https://github.com/spring-projects/spring-boot/blob/master/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java

在尝试直接运行我的 main 方法时,我也会收到“ClassNotFoundException”。

java -cp my.jar my.MainClass

有没有一种方法可以用来在 spring boot jar 中运行我的 main 方法(不是通过 JarLauncher)?

有没有人成功地将 spring-boot 与 procrun 集成?

我知道http://wrapper.tanukisoftware.com/。但是由于他们的许可证,我不能使用它。

更新

我现在已经设法使用 procrun 启动服务。

set SERVICE_NAME=MyService
set BASE_DIR=C:\MyService\Path
set PR_INSTALL=%BASE_DIR%prunsrv.exe

REM Service log configuration
set PR_LOGPREFIX=%SERVICE_NAME%
set PR_LOGPATH=%BASE_DIR%
set PR_STDOUTPUT=%BASE_DIR%stdout.txt
set PR_STDERROR=%BASE_DIR%stderr.txt
set PR_LOGLEVEL=Error

REM Path to java installation
set PR_JVM=auto
set PR_CLASSPATH=%BASE_DIR%%SERVICE_NAME%.jar

REM Startup configuration
set PR_STARTUP=auto
set PR_STARTIMAGE=c:\Program Files\Java\jre7\bin\java.exe 
set PR_STARTMODE=exe
set PR_STARTPARAMS=-jar#%PR_CLASSPATH%

REM Shutdown configuration
set PR_STOPMODE=java
set PR_STOPCLASS=TODO
set PR_STOPMETHOD=stop

REM JVM configuration
set PR_JVMMS=64
set PR_JVMMX=256

REM Install service
%PR_INSTALL% //IS//%SERVICE_NAME%

我现在只需要锻炼如何停止服务。我正在考虑使用 spring-boot 执行器关闭 JMX Bean。

当我现在停止服务时会发生什么; windows无法停止服务(但将其标记为已停止),服务仍在运行(我可以浏览到本地主机),任务管理器中没有提到该进程(不是很好!除非我是盲人)。

【问题讨论】:

  • Spring Boot 需要自定义类加载器,因为它指定了 Spring Boot jar 格式的构建(即包括嵌套的 jar)。所以不,你需要以某种方式执行java -jar my.jar 来启动服务。在 Windows 上时,您始终可以使用批处理文件来启动服务……请参阅 stackoverflow.com/questions/415409/…
  • 我怀疑是这样的。我原本以为 spring boot 只是使用了 maven shade 插件。我将按照您的链接中的建议调查 RunAsService
  • 在您可能想尝试的线程中有更多建议。 NSSM 看起来很有希望。
  • 希望我提到的解决方案对您有用,我也经历过同样的事情,并且能够轻松完成此设置,并且已经有一段时间没有问题了。

标签: spring-boot procrun


【解决方案1】:

远离winsw,它是用 .NET 制作的,我在客户环境方面遇到了很多关于过时 Windows 的问题。

我推荐NSSM,它是使用纯C 编写的,我在所有过时的Windows 上都使用了它,没有任何问题。它具有相同的功能以及更多...

这是一个batch script (.bat) 示例如何使用它:

rem Register the service
nssm install my-java-service "C:\Program Files\Java\jre1.8.0_152\bin\java.exe" "-jar" "snapshot.jar"
rem Set the service working dir
nssm set my-java-service AppDirectory "c:\path\to\jar-diretory"
rem Redirect sysout to file
nssm set my-java-service AppStdout "c:\path\to\jar-diretory\my-java-service.out"
rem Redirect syserr to file
nssm set my-java-service AppStderr "c:\path\to\jar-diretory\my-java-service.err"
rem Enable redirection files rotation
nssm set my-java-service AppRotateFiles 1
rem Rotate files while service is running
nssm set my-java-service AppRotateOnline 1
rem Rotate files when they reach 10MB
nssm set my-java-service AppRotateBytes 10485760
rem Stop service when my-java-service exits/stop
nssm set my-java-service AppExit Default Exit
rem Restart service when my-java-service exits with code 2 (self-update)
nssm set my-java-service AppExit 2 Restart
rem Set the display name for the service
nssm set my-java-service DisplayName "My JAVA Service"
rem Set the description for the service
nssm set my-java-service Description "Your Corp"
rem Remove old rotated files (older than 30 days)
nssm set my-java-service AppEvents Rotate/Pre "cmd /c forfiles /p \"c:\path\to\jar-diretory\" /s /m \"my-java-service-*.*\" /d -30 /c \"cmd /c del /q /f @path\""
rem Make a copy of my-java-service.jar to snapshot.jar to leave the original JAR unlocked (for self-update purposes)
nssm set my-java-service AppEvents Start/Pre "cmd /c copy /y \"c:\path\to\jar-diretory\my-java-service.jar\" \"c:\path\to\jar-diretory\snapshot.jar\""

【讨论】:

  • 然而我没有用 WinSW 试验不良行为,但实际上我们只使用“新”版本的 Windows。曾经,服务被 Windows 更新删除,补丁更新成功后重新安装。
【解决方案2】:

从 Spring Boot 1.3 开始,现在可以使用 winsw

documentation 将您定向到 reference implementation,它显示了如何设置服务。

【讨论】:

  • 我们切换到 WinSw 以便不需要更改代码。它似乎更简单易用!
  • 同样,我们使用 WinSw 在 Windows 操作系统(Win10 和 Win Server)上部署我们所有的 Spring Boot (2.3) 应用
【解决方案3】:

刚刚遇到这个并想分享,我不久前修复了这个问题并发出了拉取请求。 https://github.com/spring-projects/spring-boot/pull/2520

您可以使用我的分叉版本,直到它被合并以使用 procrun 启动/停止。

【讨论】:

  • 我按照github.com/spring-projects/spring-boot/issues/… 使用了您的 procrun 配置,它对我有用。我比其他答案更喜欢它,因为它不需要自定义引导类。感谢您提供此修复程序!
  • 很高兴它对您有所帮助!我希望拉取请求已被合并,但他们决定使用 MBean 进行关闭。所以仅供参考,虽然配置看起来可以正常工作,但如果您使用 spring-boot-plugin 打包您的应用程序,它不会干净地关闭,因为它会在运行 StopMethod 时创建一个全新的 ApplicationContext。这就是为什么我添加了org.springframework.boot.loader.SharedWarLauncher,它在调用开始或停止之间保持类加载器。我本可以轻松地修改 org.springframework.boot.loader.WarLauncher,但我试图不引人注意。
  • 非常感谢您的回复。我原以为一切都按预期工作,但是当我检查日志时,我发现应用程序并没有像你提到的那样干净地关闭。我的部署中不需要胖 jar,所以我切换到一个 lib 文件夹中所有 jar 的部署,并实现了一个类似于来自@ethesx 的答案的“停止”功能
【解决方案4】:

我遇到了类似的问题,但发现其他人 (Francesco Zanutto) 很亲切地写了一篇关于他们努力的博客文章。 他们的解决方案对我有用。我不相信他们为实现此代码所付出的时间。

http://zazos79.blogspot.com/2015/02/spring-boot-12-run-as-windows-service.html

与我在您的示例中看到的 exe 模式相比,他使用的是 jvm 启动和停止模式。有了这个,他能够扩展 Spring Boot 的 JarLauncher 来处理来自 Windows 服务的“启动”和“停止”命令,我相信你正在寻找优雅的关闭。

与他的示例一样,您将添加多个主要方法,具体取决于您的实现,您需要指出现在应该由启动器调用哪个方法。我正在使用 Gradle,只需将以下内容添加到我的 build.gradle:

springBoot{
    mainClass = 'mydomain.app.MyApplication'
}

我的 Procrun 安装脚本:

D:\app\prunsrv.exe //IS//MyServiceName ^
--DisplayName="MyServiceDisplayName" ^
--Description="A Java app" ^
--Startup=auto ^
--Install=%CD%\prunsrv.exe ^
--Jvm=%JAVA_HOME%\jre\bin\server\jvm.dll ^
--Classpath=%CD%\SpringBootApp-1.1.0-SNAPSHOT.jar; ^
--StartMode=jvm ^
--StartClass=mydomain.app.Bootstrap ^
--StartMethod=start ^
--StartParams=start ^
--StopMode=jvm ^
--StopClass=mydomain.app.Bootstrap ^
--StopMethod=stop ^
--StopParams=stop ^
--StdOutput=auto ^
--StdError=auto ^
--LogPath=%CD% ^
--LogLevel=Debug

JarLauncher 扩展类:

package mydomain.app;


import org.springframework.boot.loader.JarLauncher;
import org.springframework.boot.loader.jar.JarFile;

public class Bootstrap extends JarLauncher {

    private static ClassLoader classLoader = null;
    private static Bootstrap bootstrap = null;

    protected void launch(String[] args, String mainClass, ClassLoader classLoader, boolean wait)
            throws Exception {
        Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
        Thread runnerThread = new Thread(runner);
        runnerThread.setContextClassLoader(classLoader);
        runnerThread.setName(Thread.currentThread().getName());
        runnerThread.start();
        if (wait == true) {
            runnerThread.join();
        }
    }

    public static void start (String []args) {
        bootstrap = new Bootstrap ();
        try {
            JarFile.registerUrlProtocolHandler();
            classLoader = bootstrap.createClassLoader(bootstrap.getClassPathArchives());
            bootstrap.launch(args, bootstrap.getMainClass(), classLoader, true);
        }
        catch (Exception ex) {
            ex.printStackTrace();
            System.exit(1);
        }
    }

    public static void stop (String []args) {
        try {
            if (bootstrap != null) {
                bootstrap.launch(args, bootstrap.getMainClass(), classLoader, true);
                bootstrap = null;
                classLoader = null;
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
            System.exit(1);
        }
    }

    public static void main(String[] args) {
        String mode = args != null && args.length > 0 ? args[0] : null;
        if ("start".equals(mode)) {
            Bootstrap.start(args);
        }
        else if ("stop".equals(mode)) {
            Bootstrap.stop(args);
        }
    }

}

我的主要 Spring 应用类:

package mydomain.app;

import java.lang.management.ManagementFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan
@EnableAutoConfiguration
public class MyApplication {

    private static final Logger logger = LoggerFactory.getLogger(MyApplication.class);
    private static ApplicationContext applicationContext = null;

    public static void main(String[] args) {
        String mode = args != null && args.length > 0 ? args[0] : null;

        if (logger.isDebugEnabled()) {
            logger.debug("PID:" + ManagementFactory.getRuntimeMXBean().getName() + " Application mode:" + mode + " context:" + applicationContext);
        }
        if (applicationContext != null && mode != null && "stop".equals(mode)) {
            System.exit(SpringApplication.exit(applicationContext, new ExitCodeGenerator() {
                @Override
                public int getExitCode() {
                    return 0;
                }
            }));
        }
        else {
            SpringApplication app = new SpringApplication(MyApplication.class);
            applicationContext = app.run(args);
            if (logger.isDebugEnabled()) {
                logger.debug("PID:" + ManagementFactory.getRuntimeMXBean().getName() + " Application started context:" + applicationContext);
            }
        }
    }
}

【讨论】:

  • 感谢您的回答。我将部署更改为不使用 fat jar(因此没有类加载器)。我借用了你的 Spring 应用程序类,我只是将它用作 Procrun 脚本中的 StartClass 和 StopClass。
  • 这在 Spring Boot 版本 1.3.X 之前工作得非常好。在 1.4.0 中,它生成编译错误为 createMainMethodRunner(mainClass, args, classLoader);不再返回 Runnable。
  • Francesco Zanutto 为 Spring Boot 1.4 更新的解决方案在这里github.com/francesc79/test-procrun
【解决方案5】:

从 springboot v1.2.2 开始,没有干净的方法可以使用 procrun 关闭 Spring Boot 应用程序,打包为 uber jar。请务必关注这些问题,因为这也是其他人提出的问题:

目前尚不清楚 springboot 维护者是否/如何处理它。同时,考虑解压 uber jar 并忽略 Spring Boot 的 JarLauncher。

我对这个问题的原始回答(可在历史中查看)提出了一种应该有效的方法(我认为有效),但不是由于 JarLauncher 中类加载器的处理方式。

【讨论】:

  • 我的回答完全解决了你提到的这个类加载问题。拉取请求具有“共享”启动器,它们在启动和停止调用之间“共享”类加载器(即org.springframework.boot.loader.SharedWarLauncher 或在您的情况下为org.springframework.boot.loader.SharedJarLauncher)他们决定不合并,因为他们已经添加了使用 MBean 处理它的方法。
猜你喜欢
  • 1970-01-01
  • 2019-12-10
  • 2018-07-21
  • 1970-01-01
  • 1970-01-01
  • 2020-02-26
  • 1970-01-01
  • 2018-05-22
  • 2017-04-17
相关资源
最近更新 更多