欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot项目在window系统下以系统服务的方式部署jar包

程序员文章站 2024-03-17 22:34:34
...

1.部署背景

 作者的Java框架是以Spring cloud体系为基础构建的。基于Spring Boot一般有两种打包方式,一种是War包,一种是Jar包,抛开War包部署不讲,以jar包的形式部署是基于以下的考虑:

  •  A:SpringBoot本身的优势之一是内置tomcat,如果我们以war包的形式打包并部署在tomcat下,那么这个优势还有没有必要?
  •  B:官方建议使用velocity模板而不是jsp,是因为jar包形式运行对jsp支持不足,如果以war包部署,最初又何必纠结用jsp还是velecity

 这些只是作者的一点想法,写这篇文章的根本原因是,我们公司的服务器采用的是windows操作系统,并且是window server 2012版本,这跟最后决定采不采用docker有关。

 Docker for window对windows系统有要求,server版本最低要求是2016,低版本的windows系统要上docker只能使用docker toolbox,是linux容器。

 这样的话,把服务器装成linux系统不就可以了吗?何况,上docker解决的并非是根本的部署问题。

 题外话不说,这里主要解决的问题是,在windows操作系统下,如何以系统服务的方式运行jar包,令其后台运行,并且宕机时,开机自启。


2.采用技术

 通常情况下,我们在windows操作系统上部署java web程序,是以tomcat为主要web应用服务器,将项目war包放置于tomcat webapps目录下,并在bin目录下安装service.bat脚本注册window服务,运行tomcatXw.exe进行管理,这种针对的是web项目。

 SpringBoot内置tomcat,可以打包成jar包,通过主方法运行,本质上是独立的Java application,这种情况再放置于tomcat下运行,显然已不合适,当然,重新配置代码,并打成war包后仍可适用,这里我们只讲打成jar包的情况。在jar包的情况下,为此我查阅了网上资料。

 在官网文档里,是有winsw的解决方案,链接如下:https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#deployment-windows 点击打开链接,除此之外,还有一种解决方案,那就是与tomcat使用相同的技术:procrun,官方地址为:http://commons.apache.org/proper/commons-daemon/procrun.html。点击打开链接。我们这里采用的是procrun的方式。


3.个人说明

 网上有一些资料,但算不上许多,虽然我也是通过这些资料最终才实践成功的,但还是要写这篇文章,主要是因为那些文章的描述并不是很全,导致我在实践的过程中走了许多弯路,在这里,我可以负责任的说,如果你跟我有同样的需求,那么按照我的方案走,是可以解决你的问题的。


4.项目改造

 在实现window后台服务化的功能上,代码是有变动的,但这些变动并不影响项目本身以其他形式部署或运行。变动主要有以下几点。

  • A:实现window服务需要实现服务的标准,需要实现启动和停止这两个基本的接口。
  • B:SpringBoot打包和普通的jar包打包是不同的,class文件会包装在BOOT-INF下,导致正常的打包,你会class not found。so,需要使用ant辅助,只需要将主方法提取出来即可,这并不影响jar包的完整性。如此改造即可,也就是说,你需要额外建多一个类,提供下项目启动的方法,还有项目停止的方法,以及将该类可读,可以正常访问。


5.核心插件

 procrun,下载地址为:http://www.apache.org/dist/commons/daemon/binaries/windows/commons-daemon-1.1.0-bin-windows.zip。点击打开链接,我们通过该插件,将jar包注册成window服务,并和tomcat完全一致的方式将其管理起来,不同的只是配置。

6.Jar包支持

 pom.xml文件里新增loader包支持

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-loader</artifactId>
</dependency>


7.新增一个启动类

 名字自取,我这里叫Bootstrap,放哪都可以,后面配置procrun会用到。

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 {
        Thread.currentThread().setContextClassLoader(classLoader);
        Thread runnerThread = new Thread(() -> {
            try {
                createMainMethodRunner(mainClass, args, classLoader).run();
            }
            catch(Exception ex) {}
        });
        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);
        }
    }
}


8.修改SpringBoot的application类

 即SpringBoot的入口类。我的为ZooBusinessServiceApplication,修改main函数,启动时,是调用的springApplication.run,关闭时调用的是springApplication.exit,判断参数为stop字符串,这是重点。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.ApplicationContext;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

import java.lang.management.ManagementFactory;

@EnableDiscoveryClient
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableJpaAuditing
public class ZooBusinessServiceApplication {
	private static final Logger logger = LoggerFactory.getLogger(ZooBusinessServiceApplication.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(ZooBusinessServiceApplication.class);
			applicationContext = app.run(args);
			if (logger.isDebugEnabled()) {
				logger.debug("PID:" + ManagementFactory.getRuntimeMXBean().getName() + " Application started context:" + applicationContext);
			}
		}
	}
}


9.配置打包方式

 因为SpringBoot默认的打包方式比正常jar包多了BOOT-INF目录,因此使用ant将此类按照正常jar包的方式开放出来。在pom.xml里的build文件里配置,我们的主类为Bootstrap

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-antrun-plugin</artifactId>
	<version>1.8</version>
	<executions>
		<execution>
			<phase>package</phase>
			<configuration>
			 <target>
				 <zip destfile="${project.build.directory}/${project.build.finalName}.jar" update="true" compress="store">
				  <fileset dir="${project.build.directory}/classes" includes="com/ycsys/business/Bootstrap.class" />
				 </zip>
			 </target>
			</configuration>
			<goals>
			 <goal>run</goal>
			</goals>
		</execution>
	</executions>
</plugin>


10.配置procrun

 java程序上做的修改已经完毕。接下来就是配置procrun的步骤。首先我们创建一个目录,名字叫procrun,并在此目录下创建一个source目录,把下载的procrun全部丢进去。再另起一个procrun下另起一个目录,我的项目叫zoo-business-service,所以这个目录也叫zoo-business-service。另外再建一个目录,专门存放jar包,例如at-deploys-jars,加个at前缀主要是跟微信起名一样a开头可以保证显示在最前面,如下图,其他的是我其他项目,这是我自己的组织方式,不喜可按自己的方式来。

SpringBoot项目在window系统下以系统服务的方式部署jar包SpringBoot项目在window系统下以系统服务的方式部署jar包

SpringBoot项目在window系统下以系统服务的方式部署jar包


11.组织目录

 将source目录下的prunmgr.exe拷贝至项目目录下,改名为服务名.exe,我的为zoo-business-service.exe,如下图所示,这里的目录install.bat主要是服务安装批处理执行文件,start.bat是启动服务批处理执行文件,uninstall.bat是卸载服务批处理执行文件,zoo-business-service.exe是与tomcat的管理程序一样的,打开后如下图

SpringBoot项目在window系统下以系统服务的方式部署jar包SpringBoot项目在window系统下以系统服务的方式部署jar包


12.安装服务脚本

 书写install.bat,如下,默认主类为MAIN_CLASS,所跑的jar包为CLASSPATH,JAVA_HOME配置jdk,SRV配置procrun的程序地址,LOGPATH配置日志输出地址,里面的所有配置项在控制程序.exe中都有对应操作配置,都可自行修改。详细配置可自行百度procrun,有各种教程。

@echo off

rem 设置程序名称
set SERVICE_EN_NAME=zoo-business-service
set SERVICE_CH_NAME=xxxxx平台服务

rem 设置java路径
set JAVA_HOME=%JAVA_HOME%

rem 设置程序依赖及程序入口类
cd..
set BASEDIR=%CD%
set CLASSPATH=%BASEDIR%\at-deploy-jars\zoo-business-service.jar
set MAIN_CLASS=com.ycsys.business.Bootstrap

rem 设置prunsrv路径
set SRV=%BASEDIR%\source\amd64\prunsrv.exe

rem 设置日志路径及日志文件前缀
set LOGPATH=%BASEDIR%\zoo-business-service\logs

rem 输出信息
echo SERVICE_NAME: %SERVICE_EN_NAME%
echo JAVA_HOME: %JAVA_HOME%
echo MAIN_CLASS: %MAIN_CLASS%
echo prunsrv path: %SRV%

rem 设置jvm
if "%JVM%" == "" goto findJvm
if exist "%JVM%" goto foundJvm
:findJvm
set "JVM=%JAVA_HOME%\jre\bin\server\jvm.dll"
if exist "%JVM%" goto foundJvm
echo can not find jvm.dll automatically,
echo please use COMMAND to localation it
echo then install service
goto end
:foundJvm
echo 正在安装服务...
rem 安装
"%SRV%" //IS//%SERVICE_EN_NAME% --DisplayName="%SERVICE_CH_NAME%" "--Classpath=%CLASSPATH%" "--Install=%SRV%" "--JavaHome=%JAVA_HOME%" "--Jvm=%JVM%" --JvmMs=256 --JvmMx=1024 --Startup=auto --JvmOptions=-Djcifs.smb.client.dfs.disabled=false ++JvmOptions=-Djcifs.resolveOrder=DNS --StartMode=jvm --StartClass=%MAIN_CLASS% --StartMethod=start --StopMode=jvm --StopClass=%MAIN_CLASS% --StopMethod=stop --StopParams=stop --LogPath=%LOGPATH% --StdOutput=auto --StdError=auto
echo 安装服务完成。
pause


13.启动脚本

 书写start.bat

@echo off

cd..
set BASEDIR=%CD%
set SERVICE_NAME=zoo-business-server
set MONITOR_PATH=%BASEDIR%\zoo-business-server\zoo-business-server.exe

echo start %SERVICE_NAME% 

%MONITOR_PATH% //MR//%SERVICE_NAME%
echo 服务启动完成。
pause


14.卸载服务脚本

 uninstall.bat

@echo off

cd..
set basedir=%CD%
set SERVICE_NAME=zoo-business-service
set SRV=%BASEDIR%\source\amd64\prunsrv.exe
echo 正在卸载服务...
"%SRV%" //DS//%SERVICE_NAME%
echo 服务卸载完毕。
pause


15.注意

 注意,关闭配置参数,--StopParams=stop,这个参数必须要配,不然springboot程序无法正常关闭

SpringBoot项目在window系统下以系统服务的方式部署jar包


16.所有的配置及代码都已完成

 首先我们运行install.bat,然后等待服务安装完成。完成成功后,即可用服务.exe程序操作项目的启动和关闭以及自启动了。最终运行效果如图。

SpringBoot项目在window系统下以系统服务的方式部署jar包

SpringBoot项目在window系统下以系统服务的方式部署jar包


17.最后说明

以上所有代码及步骤在spring boot 1.5.9 release 版本下测试通过,批文件编码最好和本机编码一致,不然可能出现乱码。

附上我的helpme.txt.

该项目为spring boot部署为jar包形式的情况下提供在window系统部署为服务的支持,即支持jar包在window系统下部署为服务。
1.at-deploy-jars文件夹为jar包所存放的目录。
2.source文件夹为apache commons daemon procrun应用程序,与tomcat在window下的执行方式相同,核心文件,不可删。
3.其余目录为各自项目分支的文件。
4.目录下由install.bat,start.bat,uninstall.bat及监听控制程序.exe组成
5.install.bat文件为安装服务批处理文件
6.start.bat文件为启动服务批处理文件
7.uninstall.bat文件为卸载服务批处理文件。
8.操作方式:把项目安装为服务,安装完毕后打开项目控制程序.exe(项目名.exe)。start或者stop程序,注意,完整启动后方可stop,不然需任务管理器强制关闭。
9.jdk默认使用JAVA_HOME系统变量
10.日志文件在项目文件下logs文件
11.各种属性都可在控制程序中修改,如jdk可在Java.JavaVirtualMachine中修改。修改规律与tomcat配置完全一致(同一核心程序)。
12.启动与关闭实现在Bootstrap方法,stop方法需加stop字符串参数。
13.安装的服务默认开机自启。
14.更多属性配置请自行修改install.bat


18:总结:

 这种部署方式不仅仅针对SpringBoot项目,只要是Jar包,都可以这样子部署,只要实现start方法和stop方法,并告知procrun即可,适用于所有jar包注册为window服务。

ps:

 第一次写博客,其中诸多不便请原谅,有问题可以在博客上留言,看到会回复。