Spring boot Spring引导JAR作为windows服务

Spring boot Spring引导JAR作为windows服务,spring-boot,procrun,Spring Boot,Procrun,我正试着用procrun包装一个弹簧靴“uber-JAR” 按预期运行以下工作: java-jar my.jar 我需要我的spring启动jar在windows启动时自动启动。最好的解决方案是将jar作为服务运行(与独立的tomcat相同) 当我尝试运行这个程序时,我得到了“Commons Daemon procrun失败,退出值:3” 查看spring引导源代码,它似乎使用了自定义类加载器: 在尝试直接运行main方法时,我也会得到一个“ClassNotFoundException” ja

我正试着用procrun包装一个弹簧靴“uber-JAR”

按预期运行以下工作:

java-jar my.jar

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

当我尝试运行这个程序时,我得到了“Commons Daemon procrun失败,退出值:3”

查看spring引导源代码,它似乎使用了自定义类加载器:

在尝试直接运行main方法时,我也会得到一个“ClassNotFoundException”

java-cp my.jar my.main类

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

有人成功地将spring boot与procrun集成在一起了吗

我知道。但是由于他们的许可证,我不能使用它

更新

我现在已经使用ProCurn启动了服务

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无法停止该服务(但将其标记为已停止),该服务仍在运行(我可以浏览到本地主机),任务管理器中没有提到该进程(不是很好!除非我是盲人)。

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

目前还不清楚springboot维护人员是否/如何处理它。同时,考虑解压缩Uber jar并忽略Spring Bug的JavaRunter。


我对这个问题的最初回答(在历史上可以看到)提出了一种应该有效的方法(我认为是有效的),但这并不是因为JarLauncher中处理类加载器的方式。

我遇到了类似的问题,但发现其他人(Francesco Zanutto)非常慷慨地写了一篇关于他们努力的博客文章他们的解决方案对我有效。我不相信他们花了这么多时间来实现这个代码

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

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

springBoot{
    mainClass = 'mydomain.app.MyApplication'
}
我的ProLUN安装脚本:

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);
        }
    }

}
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);
            }
        }
    }
}
我的主要Spring应用程序类:

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);
        }
    }

}
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);
            }
        }
    }
}

刚刚遇到这个问题,想与大家分享,我在不久前解决了这个问题,并发出了一个拉取请求


您可以使用my forked版本,直到它被合并为使用ProCurn启动/停止。

这现在是可能的,因为Spring Boot 1.3使用了

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%
将引导您到一个显示如何设置服务的位置。

远离,它是用.NET制作的,我遇到了许多有关客户环境的问题,例如过时的Windows

我推荐,它是用纯C语言制作的,我在所有过时的窗口上都使用了它,没有任何问题。它有相同的功能和更多

下面是一个
批处理脚本(.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\""

Spring Boot需要定制类加载器,因为这是指定Spring Boot jar格式的构建(即包括嵌套jar)。因此,不,您需要以某种方式执行
java-jar my.jar
来启动服务。在windows上,您可以始终使用批处理文件来启动服务…请看,我怀疑是这样的。我原本以为spring boot只使用maven shade插件。我将按照您链接中的建议研究RunaService。您可能希望尝试的线程中有更多建议。看起来很有希望。希望我提到的解决方案对您有效,我也经历过同样的过程,能够顺利地完成此设置,并且已经有一段时间没有问题了。这可以使用Spring 1.3实现。我使用了您的procrun配置,根据,它对我有效。与其他答案相比,我更喜欢它,因为它不需要自定义引导类。感谢您提供此修复!我很高兴它帮助了你!我希望pull请求已经合并,但是他们决定使用MBean关闭。因此,仅供参考,尽管配置看起来很有效,但如果您使用spring引导插件打包应用程序,它不会完全关闭,因为它会在运行
StopMethod
时创建一个全新的应用程序上下文。这就是为什么我添加了
org.springframework.boot.loader.SharedWarLauncher
,它在调用start或stop之间保持类加载器。我本可以很容易地修改org.springframework.boot.loader.WarLauncher,但我尽量不引人注目。非常感谢您的回复。我原以为一切正常,但当我查看日志时,我发现应用程序并没有像你提到的那样完全关闭。我的部署中不需要胖jar,所以我切换到一个将所有jar放在lib文件夹中的部署,并实现了一个“停止”函数,类似于@ethesxmy answer的答案,正好解决了您提到的类加载问题。pull请求具有“共享”启动器,在启动和停止调用之间“共享”类加载器(即
org.springframework.boot.loader.SharedWarLauncher
,或者在您的情况下
org.springframework.boot.loader.SharedJarLauncher
)他们决定不合并,因为他们已经添加了处理MBean的方法。谢谢你的回答。我将部署更改为不使用fat jar(因此没有cla)